@dzhechkov/keysarium 1.1.2 → 1.3.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/README.md +7 -2
- package/package.json +10 -2
- package/templates/.claude/commands/brain-export.md +97 -20
- package/templates/.claude/commands/brain-import.md +75 -4
- package/templates/.claude/commands/dream.md +101 -0
- package/templates/.claude/commands/feature-adr.md +92 -0
- package/templates/.claude/commands/init-platform.md +161 -0
- package/templates/.claude/commands/learning-stats.md +130 -0
- package/templates/.claude/commands/workers.md +140 -0
- package/templates/.claude/rules/background-workers.md +72 -0
- package/templates/.claude/rules/dream-cycles.md +90 -0
- package/templates/.claude/rules/feature-adr-conventions.md +82 -0
- package/templates/.claude/rules/reward-learning.md +124 -0
- package/templates/.claude/shards/feature-adr.shard.md +98 -0
- package/templates/.claude/skills/feature-adr/SKILL.md +252 -0
- package/templates/.claude/skills/feature-adr/examples/sample-feature-output.md +257 -0
- package/templates/.claude/skills/feature-adr/modules/00-complexity-router.md +114 -0
- package/templates/.claude/skills/feature-adr/modules/01-requirements.md +113 -0
- package/templates/.claude/skills/feature-adr/modules/02-research.md +92 -0
- package/templates/.claude/skills/feature-adr/modules/03-adr.md +135 -0
- package/templates/.claude/skills/feature-adr/modules/04-ddd.md +114 -0
- package/templates/.claude/skills/feature-adr/modules/05-architecture.md +149 -0
- package/templates/.claude/skills/feature-adr/modules/06-implementation-plan.md +129 -0
- package/templates/.claude/skills/feature-adr/modules/07-code.md +126 -0
- package/templates/.claude/skills/feature-adr/modules/08-qe.md +167 -0
- package/templates/.claude/skills/feature-adr/references/adr-template.md +102 -0
- package/templates/.claude/skills/feature-adr/references/c4-template.md +120 -0
- package/templates/.claude/skills/feature-adr/references/complexity-matrix.md +139 -0
- package/templates/.claude/skills/feature-adr/references/qe-checklist.md +86 -0
- package/templates/CLAUDE.md +135 -8
- package/templates/docs/feature-adr/01-architecture.md +750 -0
- package/templates/docs/feature-adr/02-functionality.md +712 -0
- package/templates/docs/feature-adr/03-deployment-guide.md +614 -0
- package/templates/docs/feature-adr/04-admin-guide.md +564 -0
- package/templates/docs/feature-adr/05-user-guide.md +508 -0
- package/templates/docs/feature-adr/06-user-flows.md +1101 -0
- package/templates/docs/improvement-directions.md +16 -16
- package/templates/docs/keysarium/01-deployment-guide.md +21 -12
- package/templates/docs/keysarium/02-admin-guide.md +204 -3
- package/templates/docs/keysarium/03-user-guide.md +224 -0
- package/templates/docs/keysarium/05-architecture.md +181 -0
- package/templates/docs/keysarium/06-user-flows.md +235 -0
- package/templates/docs/keysarium-core/01-architecture.md +559 -0
- package/templates/docs/keysarium-core/02-functionality.md +953 -0
- package/templates/docs/keysarium-core/03-deployment-guide.md +625 -0
- package/templates/docs/keysarium-core/04-admin-guide.md +529 -0
- package/templates/docs/keysarium-core/05-user-guide.md +599 -0
- package/templates/docs/keysarium-core/06-user-flows.md +804 -0
- package/templates/lib/background-workers.md +278 -0
- package/templates/lib/dream-engine.md +460 -0
- package/templates/lib/judge-attestation.md +322 -0
- package/templates/lib/memory-protocol.md +344 -0
- package/templates/lib/platform-adapters.md +100 -0
- package/templates/lib/platform-templates/copilot.md +134 -0
- package/templates/lib/platform-templates/cursor.md +95 -0
- package/templates/lib/platform-templates/opencode.md +155 -0
- package/templates/lib/reward-tracker.md +256 -0
- package/templates/lib/witness-chain.md +372 -0
- package/templates/lib/worker-templates/brain-export.md +187 -0
- package/templates/lib/worker-templates/consolidate.md +180 -0
- package/templates/lib/worker-templates/dream-cycle.md +154 -0
- package/templates/lib/worker-templates/health-check.md +255 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Reward Tracker -- Analytics & Pattern Detection
|
|
2
|
+
|
|
3
|
+
Computes aggregate statistics and detects domain patterns from accumulated reward records. Used by `/learning-stats` and by `memory_query()` for pattern enrichment.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The Reward Tracker operates on the data stored by the Memory Protocol (`lib/memory-protocol.md`). It reads all RewardRecord JSON files from `.keysarium/memory/` and produces two output artifacts:
|
|
8
|
+
|
|
9
|
+
1. **reward-summary.json** -- aggregate statistics per phase, domain, and skill
|
|
10
|
+
2. **domain-patterns.json** -- detected patterns with confidence scores
|
|
11
|
+
|
|
12
|
+
## Computation Protocol
|
|
13
|
+
|
|
14
|
+
### Step 1: Load All Records
|
|
15
|
+
|
|
16
|
+
1. Scan `.keysarium/memory/` recursively for all `*.json` files (excluding config.json, domain-patterns.json, reward-summary.json).
|
|
17
|
+
2. Parse each JSON file as a RewardRecord.
|
|
18
|
+
3. Exclude records where `expires_at < current_date`.
|
|
19
|
+
4. Collect into a list sorted by `timestamp` DESC.
|
|
20
|
+
|
|
21
|
+
### Step 2: Per-Phase Reward Averages
|
|
22
|
+
|
|
23
|
+
Group records by `phase` and compute:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"phase-0": {
|
|
28
|
+
"avg_reward": 0.85,
|
|
29
|
+
"total_runs": 12,
|
|
30
|
+
"distribution": {
|
|
31
|
+
"excellent": 8,
|
|
32
|
+
"good": 3,
|
|
33
|
+
"needs_work": 1,
|
|
34
|
+
"failed": 0
|
|
35
|
+
},
|
|
36
|
+
"trend": "stable",
|
|
37
|
+
"best_case": "retail_personalization",
|
|
38
|
+
"worst_case": "bank_kc_automation"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Trend Detection Algorithm:**
|
|
44
|
+
1. Split records for each phase into two halves by timestamp (older half, newer half).
|
|
45
|
+
2. Compute average reward for each half.
|
|
46
|
+
3. If newer_avg - older_avg > 0.15 -> "improving"
|
|
47
|
+
4. If older_avg - newer_avg > 0.15 -> "degrading"
|
|
48
|
+
5. Otherwise -> "stable"
|
|
49
|
+
6. Minimum 4 records required for trend detection; otherwise "insufficient_data".
|
|
50
|
+
|
|
51
|
+
### Step 3: Per-Domain Reward Averages
|
|
52
|
+
|
|
53
|
+
Group records by `domain` and compute:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"banking": {
|
|
58
|
+
"avg_reward": 0.72,
|
|
59
|
+
"total_runs": 18,
|
|
60
|
+
"phase_breakdown": {
|
|
61
|
+
"phase-0": 0.85,
|
|
62
|
+
"phase-1": 0.90,
|
|
63
|
+
"phase-2": 0.55,
|
|
64
|
+
"phase-2.5": 0.70,
|
|
65
|
+
"phase-3": 0.65,
|
|
66
|
+
"phase-4": 0.80,
|
|
67
|
+
"phase-5": 0.72
|
|
68
|
+
},
|
|
69
|
+
"bottleneck_phase": "phase-2",
|
|
70
|
+
"strongest_phase": "phase-1"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Bottleneck Detection:**
|
|
76
|
+
- Phase with the lowest average reward in a domain is flagged as `bottleneck_phase`.
|
|
77
|
+
- Phase with the highest average reward is flagged as `strongest_phase`.
|
|
78
|
+
- Only computed if domain has 3+ records.
|
|
79
|
+
|
|
80
|
+
### Step 4: Per-Skill Effectiveness
|
|
81
|
+
|
|
82
|
+
Group records by `skill_used` and cross-reference with domain:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"goap-research-ed25519": {
|
|
87
|
+
"overall_avg": 0.74,
|
|
88
|
+
"total_runs": 15,
|
|
89
|
+
"by_domain": {
|
|
90
|
+
"banking": { "avg": 0.65, "runs": 8 },
|
|
91
|
+
"retail": { "avg": 0.85, "runs": 7 }
|
|
92
|
+
},
|
|
93
|
+
"best_domain": "retail",
|
|
94
|
+
"worst_domain": "banking"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Step 5: Domain Pattern Detection
|
|
100
|
+
|
|
101
|
+
Analyze accumulated data to detect actionable patterns. Each pattern has a confidence score based on evidence count.
|
|
102
|
+
|
|
103
|
+
**Pattern Detection Rules:**
|
|
104
|
+
|
|
105
|
+
| Rule | Condition | Pattern Template |
|
|
106
|
+
|------|-----------|-----------------|
|
|
107
|
+
| Phase Bottleneck | Phase avg < 0.5 in domain, 3+ records | "{domain} cases struggle in {phase} (avg reward: {avg})" |
|
|
108
|
+
| Phase Excellence | Phase avg > 0.9 in domain, 3+ records | "{domain} cases excel in {phase} (avg reward: {avg})" |
|
|
109
|
+
| Skill-Domain Mismatch | Skill avg < 0.5 in domain but > 0.7 in another | "{skill} underperforms in {domain} vs {other_domain}" |
|
|
110
|
+
| Improving Trend | Trend = "improving" for phase in domain | "{phase} quality is improving in {domain} domain" |
|
|
111
|
+
| Degrading Trend | Trend = "degrading" for phase in domain | "{phase} quality is degrading in {domain} -- investigate" |
|
|
112
|
+
| Time Overhead | Phase avg iterations > 2.0 in domain | "{domain} cases require more iterations in {phase}" |
|
|
113
|
+
|
|
114
|
+
**Confidence Calculation:**
|
|
115
|
+
```
|
|
116
|
+
confidence = min(1.0, evidence_count / 10)
|
|
117
|
+
```
|
|
118
|
+
Where `evidence_count` is the number of records supporting the pattern. Confidence reaches 1.0 at 10+ supporting records.
|
|
119
|
+
|
|
120
|
+
### Step 6: Write Outputs
|
|
121
|
+
|
|
122
|
+
#### reward-summary.json
|
|
123
|
+
|
|
124
|
+
Write to `.keysarium/memory/_stats/reward-summary.json`:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"version": "1.0",
|
|
129
|
+
"generated_at": "2026-03-01T14:00:00Z",
|
|
130
|
+
"total_records": 42,
|
|
131
|
+
"total_domains": 3,
|
|
132
|
+
"total_cases": 8,
|
|
133
|
+
"phase_averages": { ... },
|
|
134
|
+
"domain_averages": { ... },
|
|
135
|
+
"skill_effectiveness": { ... },
|
|
136
|
+
"overall_average": 0.76,
|
|
137
|
+
"overall_trend": "improving"
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### domain-patterns.json
|
|
142
|
+
|
|
143
|
+
Write to `.keysarium/memory/_patterns/domain-patterns.json`:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"version": "1.0",
|
|
148
|
+
"generated_at": "2026-03-01T14:00:00Z",
|
|
149
|
+
"patterns": [
|
|
150
|
+
{
|
|
151
|
+
"pattern_id": "banking-phase2-bottleneck",
|
|
152
|
+
"domain": "banking",
|
|
153
|
+
"description": "Banking cases struggle in Phase 2 (Research) with average reward 0.55",
|
|
154
|
+
"category": "phase_bottleneck",
|
|
155
|
+
"confidence": 0.80,
|
|
156
|
+
"evidence_count": 8,
|
|
157
|
+
"detected_at": "2026-03-01T14:00:00Z",
|
|
158
|
+
"examples": ["bank_kc_automation", "bank_loan_approval"],
|
|
159
|
+
"actionable_advice": "Allocate 20% extra time for Phase 2 in banking cases. Consider pre-loading regulatory templates."
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"pattern_id": "retail-triz-effective",
|
|
163
|
+
"domain": "retail",
|
|
164
|
+
"description": "TRIZ methodology produces better results than Game Theory for retail problem-solving",
|
|
165
|
+
"category": "skill_effectiveness",
|
|
166
|
+
"confidence": 0.60,
|
|
167
|
+
"evidence_count": 6,
|
|
168
|
+
"detected_at": "2026-03-01T14:00:00Z",
|
|
169
|
+
"examples": ["ecom_personalization", "retail_inventory"],
|
|
170
|
+
"actionable_advice": "Prefer TRIZ framework over Game Theory for retail domain cases in Phase 3."
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Display Format for /learning-stats
|
|
177
|
+
|
|
178
|
+
### Phase Averages Table
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
Phase Reward Averages (42 records, 8 cases)
|
|
182
|
+
───────────────────────────────────────────
|
|
183
|
+
Phase | Avg | Runs | Trend |
|
|
184
|
+
────────────|────────|──────|─────────────|
|
|
185
|
+
Phase 0 | 0.85 | 8 | stable |
|
|
186
|
+
Phase 1 | 0.92 | 8 | improving |
|
|
187
|
+
Phase 2 | 0.65 | 7 | stable |
|
|
188
|
+
Phase 2.5 | 0.78 | 6 | improving |
|
|
189
|
+
Phase 3 | 0.71 | 5 | stable |
|
|
190
|
+
Phase 4 | 0.89 | 4 | stable |
|
|
191
|
+
Phase 5 | 0.74 | 4 | stable |
|
|
192
|
+
────────────|────────|──────|─────────────|
|
|
193
|
+
Overall | 0.76 | 42 | improving |
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Domain Breakdown Table
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
Domain Breakdown
|
|
200
|
+
───────────────────────────────────────
|
|
201
|
+
Domain | Avg | Cases | Bottleneck |
|
|
202
|
+
────────────|────────|───────|─────────────|
|
|
203
|
+
Banking | 0.72 | 3 | Phase 2 |
|
|
204
|
+
Retail | 0.82 | 3 | Phase 5 |
|
|
205
|
+
Enterprise | 0.69 | 2 | Phase 3 |
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Detected Patterns
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
Detected Patterns (confidence > 0.5)
|
|
212
|
+
──────────────────────────────────────────────────────
|
|
213
|
+
1. [0.80] Banking cases struggle in Phase 2 (Research)
|
|
214
|
+
Advice: Allocate 20% extra time for Phase 2 in banking cases
|
|
215
|
+
|
|
216
|
+
2. [0.60] TRIZ outperforms Game Theory for retail problem-solving
|
|
217
|
+
Advice: Prefer TRIZ framework for retail domain in Phase 3
|
|
218
|
+
|
|
219
|
+
3. [0.55] Phase 1 quality is improving across all domains
|
|
220
|
+
Advice: Current exploration approach is working well
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Skill Effectiveness Table
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
Skill Effectiveness
|
|
227
|
+
────────────────────────────────────────────────────
|
|
228
|
+
Skill | Avg | Best Domain |
|
|
229
|
+
───────────────────────────────|──────|─────────────|
|
|
230
|
+
explore | 0.92 | retail |
|
|
231
|
+
goap-research-ed25519 | 0.74 | retail |
|
|
232
|
+
problem-solver-enhanced | 0.71 | enterprise |
|
|
233
|
+
reverse-engineering-unicorn | 0.85 | banking |
|
|
234
|
+
presentation-storyteller | 0.74 | retail |
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Empty State
|
|
238
|
+
|
|
239
|
+
When no records exist yet:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
Learning Stats
|
|
243
|
+
──────────────
|
|
244
|
+
No reward data found in .keysarium/memory/
|
|
245
|
+
|
|
246
|
+
To start collecting data:
|
|
247
|
+
1. Run a case with /casarium
|
|
248
|
+
2. Respond at each checkpoint
|
|
249
|
+
3. Rewards are automatically tracked
|
|
250
|
+
|
|
251
|
+
Memory directory: .keysarium/memory/ (will be created on first run)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Modular Reuse
|
|
255
|
+
|
|
256
|
+
This tracker is domain-agnostic. It works with any RewardRecord JSON that conforms to the schema defined in `lib/memory-protocol.md`. The pattern detection rules can be extended by adding entries to the Pattern Detection Rules table.
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# Witness Chain Protocol
|
|
2
|
+
|
|
3
|
+
> SHA-256 hash-chain for tamper-evident artifact integrity verification.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This protocol defines how to compute SHA-256 hashes of pipeline artifacts and link them into a chain. Each artifact's hash includes the previous artifact's hash, making the chain tamper-evident: modifying any artifact breaks all downstream hashes.
|
|
8
|
+
|
|
9
|
+
## Constants
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
NULL_HASH = "0000000000000000000000000000000000000000000000000000000000000000"
|
|
13
|
+
CHAIN_FILE = ".witness-chain.json"
|
|
14
|
+
HASH_PREFIX = "sha256:"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Section 1: Hash Computation
|
|
18
|
+
|
|
19
|
+
### Platform Detection
|
|
20
|
+
|
|
21
|
+
Detect the available SHA-256 command:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Try sha256sum first (Linux), then shasum (macOS)
|
|
25
|
+
if command -v sha256sum &>/dev/null; then
|
|
26
|
+
SHA_CMD="sha256sum"
|
|
27
|
+
elif command -v shasum &>/dev/null; then
|
|
28
|
+
SHA_CMD="shasum -a 256"
|
|
29
|
+
else
|
|
30
|
+
echo "ERROR: No SHA-256 command found. Install coreutils."
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Computing a File Hash
|
|
36
|
+
|
|
37
|
+
Hash a single file's content:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
HASH=$(${SHA_CMD} "path/to/file.md" | awk '{print $1}')
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Computing a Chained Hash
|
|
44
|
+
|
|
45
|
+
Hash file content concatenated with the previous hash to create a chain link:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Read file content and previous hash
|
|
49
|
+
FILE_CONTENT=$(cat "path/to/file.md")
|
|
50
|
+
PREV_HASH="<previous hash value>"
|
|
51
|
+
|
|
52
|
+
# Compute chained hash
|
|
53
|
+
CHAINED_HASH=$(printf '%s%s' "${FILE_CONTENT}" "${PREV_HASH}" | ${SHA_CMD} | awk '{print $1}')
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Important:** Use `printf '%s%s'` (not `echo`) to avoid trailing newline issues. The content is the raw file bytes followed immediately by the 64-character hex hash string.
|
|
57
|
+
|
|
58
|
+
### Content Normalization
|
|
59
|
+
|
|
60
|
+
Before hashing, the content should be the raw file content as stored on disk. Do NOT:
|
|
61
|
+
- Trim whitespace
|
|
62
|
+
- Normalize line endings
|
|
63
|
+
- Strip BOM markers
|
|
64
|
+
|
|
65
|
+
The hash must match the exact bytes in the file so that verification produces the same result.
|
|
66
|
+
|
|
67
|
+
## Section 2: Chain Operations
|
|
68
|
+
|
|
69
|
+
### Operation: Create Genesis Record
|
|
70
|
+
|
|
71
|
+
Called at Phase 0 (Discovery) to initialize the chain for a new research.
|
|
72
|
+
|
|
73
|
+
**Preconditions:**
|
|
74
|
+
- Research directory `researches/<slug>/` exists
|
|
75
|
+
- Phase 0 artifact `00_product_discovery.md` has been created
|
|
76
|
+
- No `.witness-chain.json` exists yet
|
|
77
|
+
|
|
78
|
+
**Procedure:**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
SLUG="<research-slug>"
|
|
82
|
+
DIR="researches/${SLUG}"
|
|
83
|
+
ARTIFACT="00_product_discovery.md"
|
|
84
|
+
PREV_HASH="${NULL_HASH}"
|
|
85
|
+
|
|
86
|
+
# Compute genesis hash
|
|
87
|
+
FILE_CONTENT=$(cat "${DIR}/${ARTIFACT}")
|
|
88
|
+
HASH=$(printf '%s%s' "${FILE_CONTENT}" "${PREV_HASH}" | ${SHA_CMD} | awk '{print $1}')
|
|
89
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
90
|
+
|
|
91
|
+
# Create .witness-chain.json
|
|
92
|
+
cat > "${DIR}/.witness-chain.json" << ENDOFCHAIN
|
|
93
|
+
{
|
|
94
|
+
"research_slug": "${SLUG}",
|
|
95
|
+
"created_at": "${TIMESTAMP}",
|
|
96
|
+
"last_updated": "${TIMESTAMP}",
|
|
97
|
+
"chain": [
|
|
98
|
+
{
|
|
99
|
+
"sequence": 0,
|
|
100
|
+
"phase": "phase-0",
|
|
101
|
+
"artifact": "${ARTIFACT}",
|
|
102
|
+
"hash": "${HASH_PREFIX}${HASH}",
|
|
103
|
+
"previous_hash": "${HASH_PREFIX}${PREV_HASH}",
|
|
104
|
+
"timestamp": "${TIMESTAMP}",
|
|
105
|
+
"promise_tag": "DISCOVERY_COMPLETE"
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
ENDOFCHAIN
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Operation: Append Record
|
|
113
|
+
|
|
114
|
+
Called at each subsequent phase checkpoint to add a new record to the chain.
|
|
115
|
+
|
|
116
|
+
**Preconditions:**
|
|
117
|
+
- `.witness-chain.json` exists in the research directory
|
|
118
|
+
- The new artifact file has been created
|
|
119
|
+
- The previous phase's record exists in the chain
|
|
120
|
+
|
|
121
|
+
**Procedure:**
|
|
122
|
+
|
|
123
|
+
1. Read `.witness-chain.json` and extract the last record's hash
|
|
124
|
+
2. Compute the new chained hash: `SHA-256(new_content + last_hash)`
|
|
125
|
+
3. Append the new record to the chain array
|
|
126
|
+
4. Update `last_updated` timestamp
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
SLUG="<research-slug>"
|
|
130
|
+
DIR="researches/${SLUG}"
|
|
131
|
+
ARTIFACT="<artifact-filename>"
|
|
132
|
+
PHASE="<phase-id>"
|
|
133
|
+
PROMISE="<promise-tag>"
|
|
134
|
+
|
|
135
|
+
# Get the latest hash from the chain
|
|
136
|
+
PREV_HASH=$(cat "${DIR}/.witness-chain.json" | python3 -c "
|
|
137
|
+
import json, sys
|
|
138
|
+
chain = json.load(sys.stdin)
|
|
139
|
+
last = chain['chain'][-1]
|
|
140
|
+
print(last['hash'].replace('sha256:', ''))
|
|
141
|
+
")
|
|
142
|
+
|
|
143
|
+
# Compute new hash
|
|
144
|
+
FILE_CONTENT=$(cat "${DIR}/${ARTIFACT}")
|
|
145
|
+
HASH=$(printf '%s%s' "${FILE_CONTENT}" "${PREV_HASH}" | ${SHA_CMD} | awk '{print $1}')
|
|
146
|
+
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
147
|
+
SEQUENCE=$(cat "${DIR}/.witness-chain.json" | python3 -c "
|
|
148
|
+
import json, sys
|
|
149
|
+
chain = json.load(sys.stdin)
|
|
150
|
+
print(len(chain['chain']))
|
|
151
|
+
")
|
|
152
|
+
|
|
153
|
+
# Append to chain (using python3 for safe JSON manipulation)
|
|
154
|
+
python3 -c "
|
|
155
|
+
import json, sys
|
|
156
|
+
with open('${DIR}/.witness-chain.json', 'r') as f:
|
|
157
|
+
chain = json.load(f)
|
|
158
|
+
chain['chain'].append({
|
|
159
|
+
'sequence': ${SEQUENCE},
|
|
160
|
+
'phase': '${PHASE}',
|
|
161
|
+
'artifact': '${ARTIFACT}',
|
|
162
|
+
'hash': 'sha256:${HASH}',
|
|
163
|
+
'previous_hash': 'sha256:${PREV_HASH}',
|
|
164
|
+
'timestamp': '${TIMESTAMP}',
|
|
165
|
+
'promise_tag': '${PROMISE}'
|
|
166
|
+
})
|
|
167
|
+
chain['last_updated'] = '${TIMESTAMP}'
|
|
168
|
+
with open('${DIR}/.witness-chain.json', 'w') as f:
|
|
169
|
+
json.dump(chain, f, indent=2)
|
|
170
|
+
"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Operation: Get Latest Hash
|
|
174
|
+
|
|
175
|
+
Retrieve the most recent hash from the chain (needed as input for the next append).
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
LATEST_HASH=$(python3 -c "
|
|
179
|
+
import json
|
|
180
|
+
with open('${DIR}/.witness-chain.json') as f:
|
|
181
|
+
chain = json.load(f)
|
|
182
|
+
print(chain['chain'][-1]['hash'].replace('sha256:', ''))
|
|
183
|
+
")
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Section 3: Verification
|
|
187
|
+
|
|
188
|
+
### Operation: Verify Chain
|
|
189
|
+
|
|
190
|
+
Walk the entire chain and verify each hash matches the current file content.
|
|
191
|
+
|
|
192
|
+
**Algorithm:**
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
1. Load .witness-chain.json
|
|
196
|
+
2. For each record in chain (index i):
|
|
197
|
+
a. Read the artifact file
|
|
198
|
+
b. Get previous_hash:
|
|
199
|
+
- If i == 0: previous_hash = NULL_HASH
|
|
200
|
+
- Else: previous_hash = chain[i-1].hash (without prefix)
|
|
201
|
+
c. Compute expected = SHA-256(file_content + previous_hash)
|
|
202
|
+
d. Compare expected with record.hash (without prefix)
|
|
203
|
+
e. If mismatch: record broken link
|
|
204
|
+
3. Produce verification report
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Bash implementation:**
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
DIR="researches/<slug>"
|
|
211
|
+
|
|
212
|
+
python3 << 'VERIFY_SCRIPT'
|
|
213
|
+
import json, subprocess, sys
|
|
214
|
+
|
|
215
|
+
NULL_HASH = "0" * 64
|
|
216
|
+
DIR = sys.argv[1] if len(sys.argv) > 1 else "."
|
|
217
|
+
|
|
218
|
+
def sha256(content):
|
|
219
|
+
"""Compute SHA-256 of a string."""
|
|
220
|
+
result = subprocess.run(
|
|
221
|
+
["sha256sum"],
|
|
222
|
+
input=content.encode(),
|
|
223
|
+
capture_output=True
|
|
224
|
+
)
|
|
225
|
+
if result.returncode != 0:
|
|
226
|
+
# Fallback to shasum
|
|
227
|
+
result = subprocess.run(
|
|
228
|
+
["shasum", "-a", "256"],
|
|
229
|
+
input=content.encode(),
|
|
230
|
+
capture_output=True
|
|
231
|
+
)
|
|
232
|
+
return result.stdout.decode().split()[0]
|
|
233
|
+
|
|
234
|
+
# Load chain
|
|
235
|
+
with open(f"{DIR}/.witness-chain.json") as f:
|
|
236
|
+
chain_data = json.load(f)
|
|
237
|
+
|
|
238
|
+
records = chain_data["chain"]
|
|
239
|
+
broken = []
|
|
240
|
+
verified = 0
|
|
241
|
+
|
|
242
|
+
for i, record in enumerate(records):
|
|
243
|
+
artifact_path = f"{DIR}/{record['artifact']}"
|
|
244
|
+
try:
|
|
245
|
+
with open(artifact_path) as f:
|
|
246
|
+
content = f.read()
|
|
247
|
+
except FileNotFoundError:
|
|
248
|
+
broken.append({
|
|
249
|
+
"sequence": i,
|
|
250
|
+
"artifact": record["artifact"],
|
|
251
|
+
"reason": "File not found"
|
|
252
|
+
})
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
if i == 0:
|
|
256
|
+
prev_hash = NULL_HASH
|
|
257
|
+
else:
|
|
258
|
+
prev_hash = records[i-1]["hash"].replace("sha256:", "")
|
|
259
|
+
|
|
260
|
+
expected = sha256(content + prev_hash)
|
|
261
|
+
actual = record["hash"].replace("sha256:", "")
|
|
262
|
+
|
|
263
|
+
if expected == actual:
|
|
264
|
+
verified += 1
|
|
265
|
+
else:
|
|
266
|
+
broken.append({
|
|
267
|
+
"sequence": i,
|
|
268
|
+
"artifact": record["artifact"],
|
|
269
|
+
"expected_hash": f"sha256:{expected}",
|
|
270
|
+
"actual_hash": record["hash"],
|
|
271
|
+
"reason": "Hash mismatch - artifact may have been modified"
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
# Report
|
|
275
|
+
total = len(records)
|
|
276
|
+
if not broken:
|
|
277
|
+
print(f"PASS: All {total} records verified successfully")
|
|
278
|
+
else:
|
|
279
|
+
print(f"FAIL: {len(broken)} broken links out of {total} records")
|
|
280
|
+
for b in broken:
|
|
281
|
+
print(f" - Record {b['sequence']} ({b['artifact']}): {b['reason']}")
|
|
282
|
+
|
|
283
|
+
VERIFY_SCRIPT
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Section 4: File Format
|
|
287
|
+
|
|
288
|
+
### .witness-chain.json Schema
|
|
289
|
+
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"research_slug": "string — research directory name",
|
|
293
|
+
"created_at": "ISO-8601 — when chain was initialized",
|
|
294
|
+
"last_updated": "ISO-8601 — when last record was added",
|
|
295
|
+
"chain": [
|
|
296
|
+
{
|
|
297
|
+
"sequence": "integer — 0-based position in chain",
|
|
298
|
+
"phase": "string — phase-0, phase-1, ..., phase-5",
|
|
299
|
+
"artifact": "string — filename relative to research dir",
|
|
300
|
+
"hash": "string — sha256:<64 hex chars>",
|
|
301
|
+
"previous_hash": "string — sha256:<64 hex chars> or sha256:<null hash>",
|
|
302
|
+
"timestamp": "ISO-8601 — when record was created",
|
|
303
|
+
"promise_tag": "string — semantic completion promise"
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Standard Phase-to-Artifact Mapping
|
|
310
|
+
|
|
311
|
+
| Phase | Artifact | Promise Tag |
|
|
312
|
+
|-------|----------|-------------|
|
|
313
|
+
| phase-0 | 00_product_discovery.md | DISCOVERY_COMPLETE |
|
|
314
|
+
| phase-1 | 01_case_brief.md | CASE_EXPLORED |
|
|
315
|
+
| phase-2 | 02_research_findings.md | RESEARCH_PARANOID_PASSED |
|
|
316
|
+
| phase-2.5 | 02.5_trend_brief.md | CJM_VALIDATED |
|
|
317
|
+
| phase-3 | 03_solution_strategy.md | SOLUTION_DESIGNED |
|
|
318
|
+
| phase-4 | 04_architecture.md | ARCHITECTURE_DEFINED |
|
|
319
|
+
| phase-5 | 05_presentation_content.md | PRESENTATION_READY |
|
|
320
|
+
|
|
321
|
+
### Example Complete Chain
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"research_slug": "bank_kc_automation",
|
|
326
|
+
"created_at": "2026-03-01T10:00:00Z",
|
|
327
|
+
"last_updated": "2026-03-01T14:30:00Z",
|
|
328
|
+
"chain": [
|
|
329
|
+
{
|
|
330
|
+
"sequence": 0,
|
|
331
|
+
"phase": "phase-0",
|
|
332
|
+
"artifact": "00_product_discovery.md",
|
|
333
|
+
"hash": "sha256:a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
|
|
334
|
+
"previous_hash": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
335
|
+
"timestamp": "2026-03-01T10:00:00Z",
|
|
336
|
+
"promise_tag": "DISCOVERY_COMPLETE"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"sequence": 1,
|
|
340
|
+
"phase": "phase-1",
|
|
341
|
+
"artifact": "01_case_brief.md",
|
|
342
|
+
"hash": "sha256:b2c3d4e5f67890123456789012345678901234567890123456789012345bef01",
|
|
343
|
+
"previous_hash": "sha256:a1b2c3d4e5f6789012345678901234567890123456789012345678901234abcd",
|
|
344
|
+
"timestamp": "2026-03-01T10:30:00Z",
|
|
345
|
+
"promise_tag": "CASE_EXPLORED"
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Section 5: Chain Repair
|
|
352
|
+
|
|
353
|
+
If an artifact is legitimately modified (e.g., user requested changes during checkpoint review), the chain must be repaired:
|
|
354
|
+
|
|
355
|
+
1. Re-hash the modified artifact using the previous record's hash
|
|
356
|
+
2. Update the modified record's hash in `.witness-chain.json`
|
|
357
|
+
3. Re-hash ALL subsequent records (cascade update)
|
|
358
|
+
4. Update `last_updated` timestamp
|
|
359
|
+
|
|
360
|
+
This is expected and normal. The witness chain protects against *undetected* modifications, not against all modifications.
|
|
361
|
+
|
|
362
|
+
## Section 6: Platform Compatibility
|
|
363
|
+
|
|
364
|
+
| Platform | Command | Notes |
|
|
365
|
+
|----------|---------|-------|
|
|
366
|
+
| Linux (Ubuntu, Debian, etc.) | `sha256sum` | Available by default in coreutils |
|
|
367
|
+
| macOS | `shasum -a 256` | Available by default |
|
|
368
|
+
| Windows (WSL) | `sha256sum` | Available in WSL |
|
|
369
|
+
| Windows (Git Bash) | `sha256sum` | Available in Git for Windows |
|
|
370
|
+
| Alpine Linux | `sha256sum` | Available in coreutils |
|
|
371
|
+
|
|
372
|
+
The protocol should attempt `sha256sum` first, then fall back to `shasum -a 256`.
|