@thebassclef/lite 0.1.0 → 0.1.2
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 +2 -0
- package/dist/cli.js +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/substrate/.bassclef/lite-manifest.json +999 -92
- package/substrate/.claude/hooks/longrun-prep-compounding-axis-check.sh +492 -0
- package/substrate/.claude/hooks/longrun-prep-compounding-sequence-check.sh +492 -0
- package/substrate/.claude/hooks/pre-commit-gate.sh +1 -2
- package/substrate/.claude/hooks/turn-prose-kiss-check.sh +30 -1
- package/substrate/.claude/luminaries/glenford-myers.md +230 -0
- package/substrate/.claude/luminaries/hunt-thomas.md +115 -0
- package/substrate/.claude/luminaries/hyrum-wright.md +94 -0
- package/substrate/.claude/luminaries/michael-feathers.md +2 -2
- package/substrate/.claude/luminaries/tony-hoare.md +170 -0
- package/substrate/.claude/luminaries/vaughn-vernon.md +50 -0
- package/substrate/.claude/luminaries/w-edwards-deming.md +158 -0
- package/substrate/.claude/rules/accessor-library-discipline.md +138 -0
- package/substrate/.claude/rules/adr-discipline.md +120 -0
- package/substrate/.claude/rules/api-conventions.md +125 -0
- package/substrate/.claude/rules/bootstrap-pair-discipline.md +141 -0
- package/substrate/.claude/rules/cold-adopter-harness-discipline.md +129 -0
- package/substrate/.claude/rules/compounding-axis-fresh-analysis.md +188 -0
- package/substrate/.claude/rules/compounding-sequence-fresh-analysis.md +188 -0
- package/substrate/.claude/rules/defensive-bash.md +68 -0
- package/substrate/.claude/rules/deferred-actions.md +233 -0
- package/substrate/.claude/rules/github-issue-flash-tweet.md +156 -0
- package/substrate/.claude/rules/hook-wire-on-author.md +103 -0
- package/substrate/.claude/rules/iteration-bet-brief-completeness.md +54 -0
- package/substrate/.claude/rules/lite-manifest-schema-change-discipline.md +3 -3
- package/substrate/.claude/rules/longrun-prep-plan-doc-compression.md +89 -0
- package/substrate/.claude/rules/loop-discipline.md +81 -0
- package/substrate/.claude/rules/manual-prod-approval.md +100 -0
- package/substrate/.claude/rules/marker-enrichment-discipline.md +99 -0
- package/substrate/.claude/rules/mobile-ephemeral-session.md +109 -0
- package/substrate/.claude/rules/new-dependency-check.md +51 -0
- package/substrate/.claude/rules/option-label-discipline.md +108 -0
- package/substrate/.claude/rules/pattern-annotation.md +100 -0
- package/substrate/.claude/rules/plain-english-discipline.md +11 -9
- package/substrate/.claude/rules/plan-enumeration-needs-value-props.md +211 -0
- package/substrate/.claude/rules/pr-title-shape.md +161 -0
- package/substrate/.claude/rules/prototype-workflow.md +65 -0
- package/substrate/.claude/rules/reserved-skill-names.md +123 -0
- package/substrate/.claude/rules/schema-management.md +49 -0
- package/substrate/.claude/rules/security.md +37 -0
- package/substrate/.claude/rules/skill-composition-declarations.md +124 -0
- package/substrate/.claude/rules/skill-description-clarity.md +247 -0
- package/substrate/.claude/rules/skill-procedure-step-list.md +137 -0
- package/substrate/.claude/rules/stuck-signal-diagnostic.md +140 -0
- package/substrate/.claude/rules/substrate-config-schema.md +98 -0
- package/substrate/.claude/rules/test-list-discipline.md +175 -0
- package/substrate/.claude/rules/test-sufficiency.md +210 -0
- package/substrate/.claude/rules/testing-tier-config.md +145 -0
- package/substrate/.claude/rules/testing.md +38 -0
- package/substrate/.claude/rules/turn-estimate-grounding.md +134 -0
- package/substrate/.claude/rules/visual-hierarchy.md +437 -0
- package/substrate/.claude/rules/we-dont-break-adopters.md +126 -0
- package/substrate/.claude/rules/wu-sequencing-compounds.md +145 -0
- package/substrate/.claude/skills/build/SKILL.md +1 -1
- package/substrate/.claude/skills/chronicle/SKILL.md +55 -0
- package/substrate/.claude/skills/clean-artifacts/SKILL.md +249 -0
- package/substrate/.claude/skills/decompose/SKILL.md +1 -1
- package/substrate/.claude/skills/diagnose/SKILL.md +1 -1
- package/substrate/.claude/skills/feynman/SKILL.md +90 -0
- package/substrate/.claude/skills/howdoi/SKILL.md +1 -1
- package/substrate/.claude/skills/ia-model/SKILL.md +1 -1
- package/substrate/.claude/skills/interaction-design/SKILL.md +1 -1
- package/substrate/.claude/skills/interpret-input/SKILL.md +8 -8
- package/substrate/.claude/skills/journal/SKILL.md +209 -0
- package/substrate/.claude/skills/kiss/SKILL.md +1 -1
- package/substrate/.claude/skills/launch/SKILL.md +14 -23
- package/substrate/.claude/skills/lean-canvas/SKILL.md +1 -1
- package/substrate/.claude/skills/longrun/SKILL.md +45 -8
- package/substrate/.claude/skills/luminary/SKILL.md +1 -1
- package/substrate/.claude/skills/ogilvy-writing-audit/SKILL.md +1 -1
- package/substrate/.claude/skills/onboard-repo/SKILL.md +143 -709
- package/substrate/.claude/skills/pattern-review/SKILL.md +1 -1
- package/substrate/.claude/skills/personas/SKILL.md +5 -5
- package/substrate/.claude/skills/promote/SKILL.md +1 -1
- package/substrate/.claude/skills/requirement/SKILL.md +1 -1
- package/substrate/.claude/skills/retro/SKILL.md +1 -1
- package/substrate/.claude/skills/riff/SKILL.md +1 -1
- package/substrate/.claude/skills/roadmap-reconcile/SKILL.md +1 -1
- package/substrate/.claude/skills/session-end/SKILL.md +1 -1
- package/substrate/.claude/skills/session-log/SKILL.md +3 -3
- package/substrate/.claude/skills/skills/SKILL.md +1 -1
- package/substrate/.claude/skills/spec/SKILL.md +1 -1
- package/substrate/.claude/skills/sprint/SKILL.md +1 -1
- package/substrate/.claude/skills/stage/SKILL.md +1 -1
- package/substrate/.claude/skills/state-a-problem/SKILL.md +1 -1
- package/substrate/.claude/skills/temperance/SKILL.md +1 -1
- package/substrate/.claude/skills/use-case/SKILL.md +1 -1
- package/substrate/.claude/skills/user-stories/SKILL.md +1 -1
- package/substrate/.claude/skills/value-prop/SKILL.md +1 -1
- package/substrate/.claude/skills/verify/SKILL.md +1 -1
- package/substrate/.claude/skills/visual-review/SKILL.md +503 -0
- package/substrate/.claude/skills/whats-the-plan/SKILL.md +202 -0
- package/substrate/.claude/skills/whereami/SKILL.md +2 -2
- package/substrate/CONTRIBUTING.md +1 -1
- package/substrate/README.md +5 -5
- package/substrate/lib/prose-scan-boundary.sh +171 -0
- package/substrate/lib/tier-check.sh +50 -1
- package/substrate/lib/tier-dependency-audit.sh +159 -4
- package/substrate/presence/install/bassclef-sync.template.sh +1 -1
- package/substrate/scripts/generate-lite-manifest.sh +21 -5
- package/substrate/standards/adr-template.md +86 -0
- package/substrate/standards/api-conventions/nextjs.md +84 -0
- package/substrate/standards/artifact-composition.md +209 -0
- package/substrate/standards/bash-hook-safety.md +246 -0
- package/substrate/standards/branch-stacking.md +408 -0
- package/substrate/standards/code-safety-principles.md +176 -0
- package/substrate/standards/composer-prerequisites.md +155 -0
- package/substrate/standards/dependency-discipline/cargo.md +39 -0
- package/substrate/standards/dependency-discipline/gem.md +43 -0
- package/substrate/standards/dependency-discipline/go-mod.md +41 -0
- package/substrate/standards/dependency-discipline/npm.md +42 -0
- package/substrate/standards/dependency-discipline/pip.md +42 -0
- package/substrate/standards/deployment-topology/ec2-tailscale.md +225 -0
- package/substrate/standards/deployment-topology.md +69 -0
- package/substrate/standards/docs-sync-allowlist.md +4 -4
- package/substrate/standards/domain-and-dns.md +145 -0
- package/substrate/standards/frontend-stack.md +67 -0
- package/substrate/standards/frontmatter-schema.md +154 -0
- package/substrate/standards/hook-injection-discipline.md +202 -0
- package/substrate/standards/hook-install-class.md +215 -0
- package/substrate/standards/input-handler-interface.md +152 -0
- package/substrate/standards/lite-manifest-schema-changes.md +60 -0
- package/substrate/standards/luminary-matching.md +105 -0
- package/substrate/standards/migration-discipline/active-record.md +50 -0
- package/substrate/standards/migration-discipline/alembic.md +43 -0
- package/substrate/standards/migration-discipline/gorm.md +50 -0
- package/substrate/standards/migration-discipline/prisma.md +53 -0
- package/substrate/standards/migration-discipline/sqlalchemy.md +51 -0
- package/substrate/standards/mobile-ephemeral-session.md +167 -0
- package/substrate/standards/model-routing-discipline.md +160 -0
- package/substrate/standards/persona-schema.md +229 -0
- package/substrate/standards/pluggable-luminaries.md +323 -0
- package/substrate/standards/pr-body-discipline.md +115 -0
- package/substrate/standards/preview-state-schema.md +189 -0
- package/substrate/standards/reserved-skill-names.md +120 -0
- package/substrate/standards/scannable-multi-option-output.md +261 -0
- package/substrate/standards/sdlc-gates/typescript.md +57 -0
- package/substrate/standards/session-board.md +256 -0
- package/substrate/standards/state-spine-contract.md +255 -0
- package/substrate/standards/steering-hints/kiss-words.md +11 -0
- package/substrate/standards/substrate-config-schema.md +267 -0
- package/substrate/standards/tier-dependency-analysis.md +1 -1
- package/substrate/standards/tier-tag-schema.md +1 -1
- package/substrate/standards/two-layer-config.md +99 -0
- package/substrate/standards/use-case-format.md +292 -0
- package/substrate/standards/user-story-invest.md +268 -0
- package/substrate/standards/velocity-and-appetite.md +229 -0
- package/substrate/standards/voice-input-pattern.md +119 -0
- package/substrate/standards/worktree-management.md +211 -0
- package/substrate/templates/chronicle-template.md +75 -0
- package/substrate/templates/memory-proposal-template.md +77 -0
- package/substrate/templates/persona-template.md +200 -0
- package/substrate/templates/pr-faq.md +45 -0
- package/substrate/templates/secret-rotation-template.md +162 -0
- package/substrate/templates/spec-template.md +131 -0
- package/substrate/templates/use-case-template.md +194 -0
- package/substrate/templates/user-story-template.md +107 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
tier: lite
|
|
3
|
+
name: whats-the-plan
|
|
4
|
+
description: "Declare the chain of skills you intend to run for the current task or next stretch of work. Invokable at any natural response point. Composes with /temperance (scope) and /sprint (ticket-level) \u2014 dispatch-path level."
|
|
5
|
+
problem: "Multi-skill chains dispatch without declaring the plan. Operator can't audit mid-flight."
|
|
6
|
+
value: "Declares the chain of skills you intend to run. Invokable before you start."
|
|
7
|
+
inputs: [Current task or scope description]
|
|
8
|
+
outputs: [Ordered skill chain, Rationale per step, Alternative chains]
|
|
9
|
+
user_invocable: true
|
|
10
|
+
disable_model_invocation: false
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# What's the Plan
|
|
14
|
+
|
|
15
|
+
Declare the sequence of skills, tool calls, and artifacts you intend
|
|
16
|
+
to produce for the current task. Stated **before** execution, not
|
|
17
|
+
retrospectively. Re-declared when the plan changes materially.
|
|
18
|
+
|
|
19
|
+
## When to Invoke
|
|
20
|
+
|
|
21
|
+
- **Operator asks**: "what's the plan?", "how are you going to tackle this?", "show me the chain"
|
|
22
|
+
- **Operator types**: `/whats-the-plan`
|
|
23
|
+
- **Self-invoke** (before a non-trivial chain): when the next stretch of
|
|
24
|
+
work involves ≥3 skills OR touches production code OR spans ≥1 hour.
|
|
25
|
+
This is the analogue of `/temperance` firing on the first edit — but
|
|
26
|
+
for sequencing, not scope
|
|
27
|
+
- **Plan change mid-task**: when a skill output forces a swap (new
|
|
28
|
+
interface surfaces, test fails, operator redirects) — re-declare
|
|
29
|
+
with a `was → now` delta
|
|
30
|
+
|
|
31
|
+
## What this skill is NOT
|
|
32
|
+
|
|
33
|
+
| Skill | Operates at | Time | Output |
|
|
34
|
+
|-------|-------------|------|--------|
|
|
35
|
+
| `/sprint` | Ticket/scope level | Session start | Issues + time budget + iteration name |
|
|
36
|
+
| `/temperance` | Approach/scope | Before edit | Right-thing + right-way questions |
|
|
37
|
+
| `/whats-the-plan` | **Dispatch path** | **Any natural response point** | **Ordered skill chain + swap-points** |
|
|
38
|
+
| `/chronicle` | Narrative | Session end | Retrospective log |
|
|
39
|
+
|
|
40
|
+
The four compose. A typical session:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
/sprint → "next: finish iteration X, ~60 turns"
|
|
44
|
+
/temperance → "building Y; right thing because Z; simplest path is W"
|
|
45
|
+
/whats-the-plan → "chain: /decompose → build → /verify → commit"
|
|
46
|
+
[execute]
|
|
47
|
+
/chronicle → "what happened, decisions, open threads"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Procedure
|
|
51
|
+
|
|
52
|
+
### 1. Read inputs
|
|
53
|
+
|
|
54
|
+
Before declaring, know:
|
|
55
|
+
|
|
56
|
+
- **Current task** — the thing operator just asked for, or the next
|
|
57
|
+
scope in the active iteration goal
|
|
58
|
+
- **Active iteration goal** — `docs/iteration-bets/` (latest) — governs
|
|
59
|
+
what's in-scope and what stop-points exist
|
|
60
|
+
- **Applicable gates** — any of: `/temperance`, `/diagnose`, `/verify`,
|
|
61
|
+
`/visual-review`, `/decompose` (per `sdlc-gates.md` and
|
|
62
|
+
`oo-ad-entry-point.md`)
|
|
63
|
+
- **Relevant rules** — `artifact-ingestion.md` dictates sources-read
|
|
64
|
+
block; `design-direction.md` dictates approval-respect; other rules
|
|
65
|
+
may insert steps (e.g., `prototype-workflow.md` inserts render +
|
|
66
|
+
confirm before commit for prototype edits)
|
|
67
|
+
|
|
68
|
+
### 2. Enumerate the chain
|
|
69
|
+
|
|
70
|
+
List skills, tool calls, and artifact writes in execution order. Use
|
|
71
|
+
`→` between steps. Group by phase if the chain crosses phases.
|
|
72
|
+
|
|
73
|
+
### 3. Name swap-points
|
|
74
|
+
|
|
75
|
+
A swap-point is a conditional fork: "if skill X produces finding Y,
|
|
76
|
+
insert skill Z before the next step." Swap-points are where the plan
|
|
77
|
+
may branch based on outputs you don't have yet.
|
|
78
|
+
|
|
79
|
+
List the top 2–3 swap-points the chain is sensitive to. More than 3
|
|
80
|
+
signals the chain is under-specified and needs shaping first.
|
|
81
|
+
|
|
82
|
+
### 4. State stop conditions
|
|
83
|
+
|
|
84
|
+
Where does this chain end? Options:
|
|
85
|
+
- **Operator review point** — "stop before PR open"
|
|
86
|
+
- **Gate** — "stop at /verify output; commit blocks on green"
|
|
87
|
+
- **Natural boundary** — "one WS complete; next WS is separate chain"
|
|
88
|
+
- **Time budget limit** — "stop at 20 turns; re-shape if incomplete"
|
|
89
|
+
|
|
90
|
+
### 5. State what's NOT in this chain
|
|
91
|
+
|
|
92
|
+
Adjacent work you're deliberately excluding. This is the sequencing
|
|
93
|
+
analogue of `/temperance`'s "what you're NOT doing" question.
|
|
94
|
+
|
|
95
|
+
## Output format
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
## Plan (YYYY-MM-DD HH:MM UTC)
|
|
99
|
+
|
|
100
|
+
**Task**: [one-line description]
|
|
101
|
+
**Iteration**: [goal name, or "none — ad-hoc"]
|
|
102
|
+
|
|
103
|
+
**Chain**:
|
|
104
|
+
1. /temperance — gate scope + approach
|
|
105
|
+
2. /decompose — identify interfaces (if Construction)
|
|
106
|
+
3. [build step] — edit [paths]
|
|
107
|
+
4. /verify — match change type to verification
|
|
108
|
+
5. commit — conventional message
|
|
109
|
+
|
|
110
|
+
**Swap-points**:
|
|
111
|
+
- If /decompose surfaces new cross-cutting concern → insert /pattern-review before build
|
|
112
|
+
- If /verify fails visual → insert /visual-review before commit
|
|
113
|
+
- If operator redirects scope → re-run /whats-the-plan with delta
|
|
114
|
+
|
|
115
|
+
**Stops at**: [operator review point / gate / natural boundary]
|
|
116
|
+
**Appetite**: ~N turns, ~M min
|
|
117
|
+
**Not in this chain**: [adjacent work excluded]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Re-declare format
|
|
121
|
+
|
|
122
|
+
When the plan changes materially mid-task, emit a delta (not a full
|
|
123
|
+
re-print). Material change = new skill inserted, skill removed, or
|
|
124
|
+
stop-point moved. Trivial reordering within the declared chain does
|
|
125
|
+
not trigger re-declare.
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
## Plan update (YYYY-MM-DD HH:MM UTC)
|
|
129
|
+
|
|
130
|
+
**Was**: /temperance → /decompose → build → /verify → commit
|
|
131
|
+
**Now**: /temperance → /decompose → /pattern-review → build → /verify → /visual-review → commit
|
|
132
|
+
|
|
133
|
+
**Why**: /decompose surfaced [specific interface] — triggers
|
|
134
|
+
/pattern-review swap-point. /verify scope now includes UI, pulling
|
|
135
|
+
/visual-review in per post-build gate.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Rules
|
|
139
|
+
|
|
140
|
+
- **Declare before execute**, not after. A plan declared after the
|
|
141
|
+
chain ran is a log entry, not a plan.
|
|
142
|
+
- **One plan per coherent task**, not one per turn. Multi-turn
|
|
143
|
+
execution under a single declared plan is the common case.
|
|
144
|
+
- **Re-declare on material change only**, not on every adjustment.
|
|
145
|
+
Chain chatter reduces the signal.
|
|
146
|
+
- **Plan must reference sources** (per `artifact-ingestion.md`) if the
|
|
147
|
+
chain involves producing a canvas, spec, ADR, or prototype. The
|
|
148
|
+
plan itself is a lightweight produce-ahead-of-execution artifact.
|
|
149
|
+
- **Skill chain steps presented to operator MUST follow
|
|
150
|
+
`.claude/rules/plan-enumeration-needs-value-props.md`** — each step
|
|
151
|
+
in the chain carries a tweet (claim + why-it-matters), not a bare
|
|
152
|
+
skill name. Bare "/temperance → /diagnose → /verify" is not a plan;
|
|
153
|
+
each step needs to say what it buys.
|
|
154
|
+
- **Do not declare a plan you know you won't follow**. If the chain is
|
|
155
|
+
genuinely uncertain, say so: "Plan unclear; running /temperance +
|
|
156
|
+
/diagnose first to shape." That is itself a valid plan.
|
|
157
|
+
|
|
158
|
+
## Chronicle integration
|
|
159
|
+
|
|
160
|
+
When the session chronicle is written, the main-task plan declaration
|
|
161
|
+
SHOULD be linked or quoted. Plan → execution → reference-back is the
|
|
162
|
+
loop that makes plan declaration load-bearing rather than theatrical.
|
|
163
|
+
|
|
164
|
+
If the plan changed during execution, chronicle notes the delta
|
|
165
|
+
(which swap-points fired, which didn't).
|
|
166
|
+
|
|
167
|
+
## Operator-facing behavior
|
|
168
|
+
|
|
169
|
+
When operator types `/whats-the-plan`:
|
|
170
|
+
- If a plan was declared recently (same task, no material change):
|
|
171
|
+
re-display the current plan
|
|
172
|
+
- If no plan exists or task has shifted: run the procedure and declare
|
|
173
|
+
- If operator asks "what's the plan?" in conversation: same procedure,
|
|
174
|
+
slight framing adjustment — phrase as response, not bassclef output
|
|
175
|
+
block, but include the chain + swap-points + stops-at
|
|
176
|
+
|
|
177
|
+
## Relationship to other rules
|
|
178
|
+
|
|
179
|
+
- `sdlc-gates.md` — declared plan must include gate skills where
|
|
180
|
+
applicable; hook-enforced gates are not optional plan steps
|
|
181
|
+
- `artifact-ingestion.md` — plans that will produce generative output
|
|
182
|
+
must name sources the chain will read
|
|
183
|
+
- `design-direction.md` — plan changes after operator approval require
|
|
184
|
+
operator reconciliation, not agent-unilateral re-planning
|
|
185
|
+
- `oo-ad-entry-point.md` — Construction chains that edit `src/` must
|
|
186
|
+
include `/decompose` or explicit trivial-work deferral
|
|
187
|
+
- `iteration-goal-brief-completeness.md` — plans delegated to subagents
|
|
188
|
+
are briefs; this skill outputs the brief
|
|
189
|
+
|
|
190
|
+
## Enforcement
|
|
191
|
+
|
|
192
|
+
V1: methodology-level. Operator invokes or Claude self-invokes based
|
|
193
|
+
on the triggers above.
|
|
194
|
+
|
|
195
|
+
V2 (future): rule `plan-declaration-required.md` + hook that fires
|
|
196
|
+
BLOCKED when a ≥3-skill chain executes without a preceding
|
|
197
|
+
`/whats-the-plan` output in session. Not in v1 scope — shipping the
|
|
198
|
+
skill first lets us learn where auto-fire helps vs. spams.
|
|
199
|
+
|
|
200
|
+
## Output discipline
|
|
201
|
+
|
|
202
|
+
Dispatch `/kiss words --rewrite` on your skill output before you return it. See `standards/skill-output-discipline.md` for the contract.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: whereami
|
|
3
|
-
description: "Show project state in one glance
|
|
3
|
+
description: "Show project state in one glance \u2014 phase, active goal, agents in flight, subsystem progress, gate checklist, freshness. Reads docs/whereami.md. Run at session start before /sprint to orient."
|
|
4
4
|
problem: "Project state lives across whereami and iteration goals and chronicles and git. No single-glance view."
|
|
5
5
|
value: "Phase and active goal and agents in flight and gate progress \u2014 one page."
|
|
6
6
|
inputs: [Optional freshness flag]
|
|
@@ -41,7 +41,7 @@ Four skills, four altitudes:
|
|
|
41
41
|
| `/whereami` | Project state | Phase + goal + agents + freshness |
|
|
42
42
|
| `/sprint` | Ticket / scope | Open issues + proposed sprint |
|
|
43
43
|
| `/whats-the-plan` | Dispatch path | Ordered skill chain |
|
|
44
|
-
| `/substrate-check` | Wiring | PASS / WARN / FAIL on
|
|
44
|
+
| `/substrate-check` | Wiring | PASS / WARN / FAIL on files + hooks + CLIs |
|
|
45
45
|
|
|
46
46
|
Session-start order: `/whereami`, then `/sprint`, then `/whats-the-plan`, then execute.
|
|
47
47
|
|
|
@@ -40,7 +40,7 @@ Not sure which surface fits? The `/skill` skill runs a taxonomy gate (skill / ru
|
|
|
40
40
|
|
|
41
41
|
### Operator-private vs adopter-facing
|
|
42
42
|
|
|
43
|
-
The `docs/operator-private/` directory in your clone contains only a README placeholder. That's intentional — operator-private substrate (internal deliberation, strategy notes) lives in a separate private repo. Contributors don't need to populate or interact with it.
|
|
43
|
+
The `docs/operator-private/` directory in your clone contains only a README placeholder. That's intentional — operator-private substrate (internal deliberation, strategy notes) lives in a separate private repo. Contributors don't need to populate or interact with it. The discipline: strategy artifacts stay private; only reference substrate (rules, hooks, standards) ships to the public bassclef repo.
|
|
44
44
|
|
|
45
45
|
If you're the bassclef maintainer (operator), `scripts/operator-setup.sh` sets up the symlink workflow when the private repo is cloned at the expected sibling location. The script is idempotent + a no-op for adopter clones; adopters can ignore it.
|
|
46
46
|
|
package/substrate/README.md
CHANGED
|
@@ -19,7 +19,7 @@ It is not a product. It is not a SaaS. It's a public framework, Apache 2.0: clon
|
|
|
19
19
|
|
|
20
20
|
## Who this is for
|
|
21
21
|
|
|
22
|
-
**Solo system / app / portfolio operators.** The person running multiple software projects in parallel — apps, internal systems, side bets, family operations — around a day job, around family, around the other things that don't pause. Real deadlines, real stakes, no headcount. Other audiences may benefit; this is the one SENTINEL_bassclef_commits to.
|
|
22
|
+
**Solo system / app / portfolio operators.** The person running multiple software projects in parallel — apps, internal systems, side bets, family operations — around a day job, around family, around the other things that don't pause. Real deadlines, real stakes, no headcount. Other audiences may benefit; this is the one SENTINEL_bassclef_commits to.
|
|
23
23
|
|
|
24
24
|
## Why this exists
|
|
25
25
|
|
|
@@ -38,7 +38,7 @@ The acceptance test for v1 wasn't a demo — it was a live ticket from my parent
|
|
|
38
38
|
|
|
39
39
|
Not theoretical. Bassclef runs today across eight repos in active use:
|
|
40
40
|
|
|
41
|
-
- **A private sibling repo for strategy notes** — canvases, audits, and locked decisions.
|
|
41
|
+
- **A private sibling repo for strategy notes** — canvases, audits, and locked decisions. Adopters replicate the pattern with a separate private repo cloned alongside their bassclef checkout.
|
|
42
42
|
- **Seven application repos in daily use** — from a power-of-attorney tracker for my family to a collaborative travel planner to a recipe database. Each one is code I use myself, not a demo.
|
|
43
43
|
|
|
44
44
|
The pattern: build the apps on the go, each repo's friction feeds back into bassclef as evolution tickets, the substrate hardens with every shipped feature.
|
|
@@ -165,9 +165,9 @@ Bassclef is markdown + bash + git. If you can read a file and run a script, you
|
|
|
165
165
|
- [Skills catalog](https://github.com/sunj-labs/bassclef/blob/main/docs/skills-catalog.md) · [Hooks catalog](https://github.com/sunj-labs/bassclef/blob/main/docs/hooks-catalog.md) · [Rules catalog](https://github.com/sunj-labs/bassclef/blob/main/docs/rules-catalog.md) — substrate catalogs
|
|
166
166
|
- [Context engineering framing](https://github.com/sunj-labs/bassclef/blob/main/design/bassclef-as-context-engineering.md) — substrate-truth framing
|
|
167
167
|
- [Context engineering rule](https://github.com/sunj-labs/bassclef/blob/main/.claude/rules/context-engineering.md) — the foundational rule
|
|
168
|
-
-
|
|
169
|
-
-
|
|
168
|
+
- Agent choreography — multi-agent operational guide (operator substrate)
|
|
169
|
+
- whereami — current project state (operator substrate)
|
|
170
170
|
|
|
171
171
|
## Operator-private substrate
|
|
172
172
|
|
|
173
|
-
The `docs/operator-private/` directory contains only a README placeholder in adopter clones. Operator-private substrate (internal deliberation, strategy notes) lives
|
|
173
|
+
The `docs/operator-private/` directory contains only a README placeholder in adopter clones. Operator-private substrate (internal deliberation, strategy notes) lives in a separate private repo cloned alongside bassclef.
|
|
@@ -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
|
+
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Rules governed by:
|
|
16
16
|
# - .claude/rules/testing-tier-config.md (Tier 0 strict TDD)
|
|
17
|
-
# -
|
|
17
|
+
# - mechanism-fidelity rule (standard tier; interface honored end to end)
|
|
18
18
|
#
|
|
19
19
|
# Anchor luminaries: ousterhout (deep module), feathers (probe pin behavior)
|
|
20
20
|
|
|
@@ -117,6 +117,55 @@ except Exception:
|
|
|
117
117
|
PYEOF
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
# resolve_file_tier <path>
|
|
121
|
+
#
|
|
122
|
+
# Read a file's tier tag and print it to stdout. Returns empty string if
|
|
123
|
+
# no tag is present. Supported shapes:
|
|
124
|
+
# - .md — YAML frontmatter `tier:` field between --- markers
|
|
125
|
+
# - .sh, .py — `# tier:` header line in first 10 lines
|
|
126
|
+
# - .yml, .yaml — `# tier:` comment OR top-level `tier:` field in first 30 lines
|
|
127
|
+
# - .json — root-key `"tier"` or `"_tier"` in first 30 lines
|
|
128
|
+
# - .jsonc — `// tier:` comment or root-key
|
|
129
|
+
# - .txt — first-line `# tier:` convention
|
|
130
|
+
#
|
|
131
|
+
# Mirrors detection shape from .claude/hooks/tier-tag-required-at-write.sh so
|
|
132
|
+
# the reader half and the writer half stay consistent.
|
|
133
|
+
resolve_file_tier() {
|
|
134
|
+
local path="$1"
|
|
135
|
+
if [ ! -f "$path" ]; then
|
|
136
|
+
return 0
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
local tier=""
|
|
140
|
+
case "$path" in
|
|
141
|
+
*.md)
|
|
142
|
+
# Frontmatter between --- markers, first 30 lines
|
|
143
|
+
tier=$(awk '
|
|
144
|
+
/^---/ { in_fm = !in_fm; if (!in_fm) exit; next }
|
|
145
|
+
in_fm && /^tier:[[:space:]]/ { sub(/^tier:[[:space:]]*/, ""); sub(/[[:space:]]*$/, ""); print; exit }
|
|
146
|
+
NR > 30 { exit }
|
|
147
|
+
' "$path" 2>/dev/null)
|
|
148
|
+
;;
|
|
149
|
+
*.sh|*.py)
|
|
150
|
+
tier=$(head -10 "$path" 2>/dev/null | grep -E '^#[[:space:]]*tier:[[:space:]]' | head -1 | sed -E 's/^#[[:space:]]*tier:[[:space:]]*//; s/[[:space:]]*$//')
|
|
151
|
+
;;
|
|
152
|
+
*.yml|*.yaml)
|
|
153
|
+
tier=$(head -30 "$path" 2>/dev/null | grep -E '^#[[:space:]]*tier:[[:space:]]|^tier:[[:space:]]' | head -1 | sed -E 's/^#?[[:space:]]*tier:[[:space:]]*//; s/[[:space:]]*$//')
|
|
154
|
+
;;
|
|
155
|
+
*.json|*.jsonc)
|
|
156
|
+
tier=$(head -30 "$path" 2>/dev/null | grep -oE '"(_?tier)"[[:space:]]*:[[:space:]]*"[a-z]+"' | head -1 | sed -E 's/.*:[[:space:]]*"([a-z]+)"/\1/')
|
|
157
|
+
if [ -z "$tier" ]; then
|
|
158
|
+
tier=$(head -30 "$path" 2>/dev/null | grep -oE '//[[:space:]]*tier:[[:space:]]*[a-z]+' | head -1 | sed -E 's/.*tier:[[:space:]]*//')
|
|
159
|
+
fi
|
|
160
|
+
;;
|
|
161
|
+
*.txt)
|
|
162
|
+
tier=$(head -1 "$path" 2>/dev/null | grep -E '^#[[:space:]]*tier:[[:space:]]' | sed -E 's/^#[[:space:]]*tier:[[:space:]]*//; s/[[:space:]]*$//')
|
|
163
|
+
;;
|
|
164
|
+
esac
|
|
165
|
+
|
|
166
|
+
echo "$tier"
|
|
167
|
+
}
|
|
168
|
+
|
|
120
169
|
# tier_at_least <required> [current]
|
|
121
170
|
#
|
|
122
171
|
# Returns 0 if current tier >= required, 1 otherwise.
|
|
@@ -859,11 +859,24 @@ walk_graph_bfs_inbound() {
|
|
|
859
859
|
compute_kind_coverage() {
|
|
860
860
|
local walk_file="$1"
|
|
861
861
|
local tier="$2"
|
|
862
|
-
#
|
|
863
|
-
|
|
864
|
-
|
|
862
|
+
# Derive known kinds from REFERENCE_KINDS at run time so new
|
|
863
|
+
# extractor rows automatically appear in coverage. Pre-fix this
|
|
864
|
+
# list was hardcoded and drifted from REFERENCE_KINDS — hook_ref
|
|
865
|
+
# existed as a walked kind but was missing from coverage output
|
|
866
|
+
# (bug 1 per docs/audits/2026-09-10-tier-graph-walk-miss.md).
|
|
867
|
+
# schema_ref is not in REFERENCE_KINDS but has its own extractor
|
|
868
|
+
# per _extract_schema_ref; keep it as a static append so schema
|
|
869
|
+
# coverage still emits.
|
|
870
|
+
local known_kinds=()
|
|
871
|
+
local entry name
|
|
872
|
+
for entry in "${REFERENCE_KINDS[@]}"; do
|
|
873
|
+
name="${entry%%|*}"
|
|
874
|
+
known_kinds+=("$name")
|
|
875
|
+
done
|
|
876
|
+
known_kinds+=("schema_ref")
|
|
877
|
+
local kind count
|
|
865
878
|
for kind in "${known_kinds[@]}"; do
|
|
866
|
-
|
|
879
|
+
count=0
|
|
867
880
|
if [ -s "$walk_file" ]; then
|
|
868
881
|
count="$(awk -F'\t' -v k="$kind" '$1 == k { c++ } END { print c+0 }' "$walk_file")"
|
|
869
882
|
fi
|
|
@@ -931,3 +944,145 @@ read_intended_source_1_extended() {
|
|
|
931
944
|
if [ -n "$out" ]; then echo "$out"; fi
|
|
932
945
|
return 0
|
|
933
946
|
}
|
|
947
|
+
|
|
948
|
+
# ---------------------------------------------------------------------------
|
|
949
|
+
# G5 extension (goal 2026-09-09b): per-edge tier alignment check
|
|
950
|
+
# Ships in PR-2 alongside Bug 1 fix for coverage reporter. Closes the
|
|
951
|
+
# gap Shape e was designed to catch — SKILL body references to hooks
|
|
952
|
+
# tagged at higher tier than the SKILL itself (broken adopter promise
|
|
953
|
+
# per @luminary linus-torvalds and ADR-043 D1 concentric inclusion).
|
|
954
|
+
# ---------------------------------------------------------------------------
|
|
955
|
+
|
|
956
|
+
# _tier_rank_full — full 5-tier rank map per ADR-043 D1
|
|
957
|
+
# lite < standard < standard-pro < ultra < upstream
|
|
958
|
+
# Rank 0 for unknown or missing tier (treated as neutral).
|
|
959
|
+
_tier_rank_full() {
|
|
960
|
+
case "$1" in
|
|
961
|
+
lite) echo 1 ;;
|
|
962
|
+
standard) echo 2 ;;
|
|
963
|
+
standard-pro) echo 3 ;;
|
|
964
|
+
ultra) echo 4 ;;
|
|
965
|
+
upstream) echo 5 ;;
|
|
966
|
+
*) echo 0 ;;
|
|
967
|
+
esac
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
# _read_file_tier — extract tier tag from a file
|
|
971
|
+
# Mirrors scripts/release-to-bassclef.sh:read_tier per standards/tier-tag-schema.md
|
|
972
|
+
# Returns tier name on stdout; empty when no tag present.
|
|
973
|
+
_read_file_tier() {
|
|
974
|
+
local f="$1"
|
|
975
|
+
[ -f "$f" ] || return 0
|
|
976
|
+
case "$f" in
|
|
977
|
+
*.md)
|
|
978
|
+
awk '
|
|
979
|
+
BEGIN{infm=0}
|
|
980
|
+
NR==1 && /^---/ {infm=1; next}
|
|
981
|
+
infm && /^---/ {exit}
|
|
982
|
+
infm && /^tier:[[:space:]]/ {sub(/^tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}
|
|
983
|
+
' "$f"
|
|
984
|
+
;;
|
|
985
|
+
*.sh|*.py)
|
|
986
|
+
head -5 "$f" | awk '/^# tier:[[:space:]]/ {sub(/^# tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}'
|
|
987
|
+
;;
|
|
988
|
+
*.yml|*.yaml)
|
|
989
|
+
head -5 "$f" | awk '
|
|
990
|
+
/^---/ {next}
|
|
991
|
+
/^# tier:[[:space:]]/ {sub(/^# tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}
|
|
992
|
+
/^tier:[[:space:]]/ {sub(/^tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}
|
|
993
|
+
'
|
|
994
|
+
;;
|
|
995
|
+
*.jsonc)
|
|
996
|
+
head -5 "$f" | awk '/^\/\/ tier:[[:space:]]/ {sub(/^\/\/ tier:[[:space:]]*/,""); gsub(/[[:space:]]/,""); print; exit}'
|
|
997
|
+
;;
|
|
998
|
+
*.json)
|
|
999
|
+
head -5 "$f" | awk '
|
|
1000
|
+
/"tier":[[:space:]]*"[^"]+"/ {
|
|
1001
|
+
match($0, /"tier":[[:space:]]*"[^"]+"/); s=substr($0, RSTART, RLENGTH);
|
|
1002
|
+
gsub(/"tier":[[:space:]]*"/, "", s); gsub(/"$/, "", s);
|
|
1003
|
+
print s; exit
|
|
1004
|
+
}
|
|
1005
|
+
/"_tier":[[:space:]]*"[^"]+"/ {
|
|
1006
|
+
match($0, /"_tier":[[:space:]]*"[^"]+"/); s=substr($0, RSTART, RLENGTH);
|
|
1007
|
+
gsub(/"_tier":[[:space:]]*"/, "", s); gsub(/"$/, "", s);
|
|
1008
|
+
print s; exit
|
|
1009
|
+
}
|
|
1010
|
+
'
|
|
1011
|
+
;;
|
|
1012
|
+
*) : ;;
|
|
1013
|
+
esac
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
# check_tier_alignment_on_edges — per-edge tier alignment check
|
|
1017
|
+
# Interface: ITierAlignmentChecker (per Larman GRASP naming)
|
|
1018
|
+
# Inputs: $1 = walk-with-kinds.tsv path (rows: <kind>\t<from>\t<to>)
|
|
1019
|
+
# $2 = target tier (e.g. "lite")
|
|
1020
|
+
# $3 = optional out-file for violation report (default: stdout)
|
|
1021
|
+
# Outputs: writes violations as "<kind>\t<from>\t<from-tier>\t<to>\t<to-tier>"
|
|
1022
|
+
# Exit: 0 always. Count of violations on final stderr line.
|
|
1023
|
+
# Rule: ADR-043 D1 concentric inclusion — source tier T guarantees
|
|
1024
|
+
# target tier <= T. When source has target tier AND target
|
|
1025
|
+
# file has HIGHER tier rank, source's tier promise breaks.
|
|
1026
|
+
check_tier_alignment_on_edges() {
|
|
1027
|
+
local walk_file="$1"
|
|
1028
|
+
local target_tier="$2"
|
|
1029
|
+
local out_file="${3:-/dev/stdout}"
|
|
1030
|
+
[ -f "$walk_file" ] || { echo "check_tier_alignment_on_edges: walk file missing at $walk_file" >&2; return 1; }
|
|
1031
|
+
local target_rank
|
|
1032
|
+
target_rank="$(_tier_rank_full "$target_tier")"
|
|
1033
|
+
[ "$target_rank" = "0" ] && { echo "check_tier_alignment_on_edges: unknown target tier '$target_tier'" >&2; return 1; }
|
|
1034
|
+
# Optimization: cache tiers per unique file. Then use awk with an
|
|
1035
|
+
# associative array for O(1) lookup during edge walk. Prior shape
|
|
1036
|
+
# ran awk per edge lookup = O(edges * cache) ~ 10M ops on lite tier.
|
|
1037
|
+
# Initialize local var so `set -u` callers do not trip on the trap
|
|
1038
|
+
# when mktemp fails or the local declaration races against the trap.
|
|
1039
|
+
local tier_cache=""
|
|
1040
|
+
tier_cache="$(mktemp)" || tier_cache="/tmp/tier_cache_fallback_$$"
|
|
1041
|
+
# shellcheck disable=SC2064
|
|
1042
|
+
trap "rm -f '${tier_cache}'" RETURN
|
|
1043
|
+
# First pass: collect unique files then read tier per file (bash loop).
|
|
1044
|
+
local file tier
|
|
1045
|
+
while IFS= read -r file; do
|
|
1046
|
+
[ -n "$file" ] || continue
|
|
1047
|
+
tier="$(_read_file_tier "$file" 2>/dev/null || true)"
|
|
1048
|
+
printf "%s\t%s\n" "$file" "${tier:-none}"
|
|
1049
|
+
done < <(awk -F'\t' '{print $2; print $3}' "$walk_file" | sort -u) > "$tier_cache"
|
|
1050
|
+
# Second pass: awk-side associative array; single sweep of walk_file.
|
|
1051
|
+
local violations
|
|
1052
|
+
violations="$(awk -F'\t' \
|
|
1053
|
+
-v target="$target_tier" \
|
|
1054
|
+
-v target_rank="$target_rank" \
|
|
1055
|
+
-v out="$out_file" \
|
|
1056
|
+
-v cache="$tier_cache" '
|
|
1057
|
+
BEGIN {
|
|
1058
|
+
# Preload rank map
|
|
1059
|
+
rank["lite"] = 1; rank["standard"] = 2; rank["standard-pro"] = 3;
|
|
1060
|
+
rank["ultra"] = 4; rank["upstream"] = 5;
|
|
1061
|
+
# Preload tier cache
|
|
1062
|
+
while ((getline line < cache) > 0) {
|
|
1063
|
+
n = split(line, parts, "\t")
|
|
1064
|
+
if (n >= 2) tier_of[parts[1]] = parts[2]
|
|
1065
|
+
}
|
|
1066
|
+
close(cache)
|
|
1067
|
+
v = 0
|
|
1068
|
+
# Truncate out file
|
|
1069
|
+
printf "" > out
|
|
1070
|
+
}
|
|
1071
|
+
{
|
|
1072
|
+
kind = $1; from = $2; to = $3
|
|
1073
|
+
if (kind == "" || from == "" || to == "") next
|
|
1074
|
+
ft = tier_of[from]
|
|
1075
|
+
tt = tier_of[to]
|
|
1076
|
+
if (ft != target) next
|
|
1077
|
+
tr = rank[tt] + 0
|
|
1078
|
+
if (tr == 0) next
|
|
1079
|
+
if (tr > target_rank) {
|
|
1080
|
+
print kind "\t" from "\t" ft "\t" to "\t" tt >> out
|
|
1081
|
+
v++
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
END { print v }
|
|
1085
|
+
' "$walk_file")"
|
|
1086
|
+
echo "check_tier_alignment_on_edges: $violations violations for target_tier=$target_tier" >&2
|
|
1087
|
+
return 0
|
|
1088
|
+
}
|