elliot-stack 1.0.29 → 1.0.33
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/LICENSE +21 -21
- package/README.md +5 -0
- package/bin/install.cjs +981 -950
- package/hooks/repo-search-nudge.js +32 -32
- package/package.json +1 -1
- package/skills/estack-active-learning-tutor/SKILL.md +339 -339
- package/skills/estack-better-title/SKILL.md +64 -64
- package/skills/estack-better-title/scripts/rename.sh +55 -55
- package/skills/estack-chris-voss/SKILL.md +80 -80
- package/skills/estack-chris-voss/references/elliot-notes.md +120 -120
- package/skills/estack-chris-voss/references/voss-principles.md +210 -210
- package/skills/estack-customer-discovery/SKILL.md +60 -60
- package/skills/estack-flight-planner/SKILL.md +332 -332
- package/skills/estack-flight-planner/references/config_schema.md +156 -156
- package/skills/estack-flight-planner/references/flight_history_schema.md +97 -97
- package/skills/estack-flight-planner/references/shuttle_schedules.md +98 -98
- package/skills/estack-flight-planner/scripts/check_setup.sh +89 -89
- package/skills/estack-flight-planner/scripts/fetch_flights.py +99 -99
- package/skills/estack-flight-planner/scripts/filter_flights.py +265 -265
- package/skills/estack-flight-planner/scripts/pair_shuttles.py +173 -173
- package/skills/estack-github-issue-tracker/SKILL.md +322 -322
- package/skills/estack-github-issue-tracker/bin/tracker-tools.cjs +1358 -1358
- package/skills/estack-github-issue-tracker/references/gh-cli-patterns.md +124 -124
- package/skills/estack-github-issue-tracker/references/result-file-schema.md +156 -156
- package/skills/estack-github-issue-tracker/references/tracker-schema.md +96 -96
- package/skills/estack-github-issue-tracker/tracker-template.md +58 -58
- package/skills/estack-leadership-coach/SKILL.md +235 -0
- package/skills/estack-leadership-coach/adding-references.md +280 -0
- package/skills/estack-leadership-coach/frameworks/delegation/flows/post-mortem.md +120 -0
- package/skills/estack-leadership-coach/frameworks/delegation/flows/pre-delegation.md +138 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/1-intake.md +145 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/2-trm-assessment.md +119 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/3-enrollment.md +132 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/4-build-brief.md +171 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/5-monitoring.md +134 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/6-reverse-delegation.md +118 -0
- package/skills/estack-leadership-coach/frameworks/delegation/phases/7-diagnose.md +200 -0
- package/skills/estack-leadership-coach/references/.source-files/deci-ryan_self-determination-theory__deci-olafsen-ryan-2017-self-determination-theory-in-work-organizations.md +1881 -0
- package/skills/estack-leadership-coach/references/.source-files/deci-ryan_self-determination-theory__gagne-deci-2005-self-determination-theory-and-work-motivation.md +2058 -0
- package/skills/estack-leadership-coach/references/.source-files/deci-ryan_self-determination-theory__selfdeterminationtheory-org-theory-overview-page.md +61 -0
- package/skills/estack-leadership-coach/references/.source-files/gallup_engagement-research__gallup-3-key-insights-into-the-global-workplace-2024.md +57 -0
- package/skills/estack-leadership-coach/references/.source-files/gallup_engagement-research__gallup-managers-account-for-70-percent-of-variance-in-employee-engagement-2015.md +40 -0
- package/skills/estack-leadership-coach/references/.source-files/gallup_engagement-research__gallup-state-of-the-global-workplace-2026-global-data-summary.md +73 -0
- package/skills/estack-leadership-coach/references/.source-files/gallup_engagement-research__gallup-state-of-the-global-workplace-2026-report-landing.md +42 -0
- package/skills/estack-leadership-coach/references/.source-files/hormozi-leila_4-stages__leila-hormozi-the-art-of-delegation-blog-post.md +91 -0
- package/skills/estack-leadership-coach/references/.source-files/oncken-wass_monkeys-hbr-1974__oncken-wass-management-time-whos-got-the-monkey-hbr-classic-1974.md +969 -0
- package/skills/estack-leadership-coach/references/.source-files/sanchez_main-street-millionaire__codie-sanchez-afford-anything-podcast-ep-565-show-notes.md +89 -0
- package/skills/estack-leadership-coach/references/.source-files/sullivan_who-not-how__dan-sullivan-impact-filter-tool-and-guide-booklet.md +565 -0
- package/skills/estack-leadership-coach/references/.source-files/van-edwards_cues__vanessa-van-edwards-lewis-howes-school-of-greatness-ep-1231-show-notes.md +122 -0
- package/skills/estack-leadership-coach/references/.source-files/van-edwards_cues__vanessa-van-edwards-roger-dooley-cues-interview.md +194 -0
- package/skills/estack-leadership-coach/references/deci-ryan_self-determination-theory.md +166 -0
- package/skills/estack-leadership-coach/references/doerr_measure-what-matters.md +154 -0
- package/skills/estack-leadership-coach/references/ferriss_4hww.md +189 -0
- package/skills/estack-leadership-coach/references/gallup_engagement-research.md +105 -0
- package/skills/estack-leadership-coach/references/gerber_e-myth-revisited.md +118 -0
- package/skills/estack-leadership-coach/references/grove_high-output-management.md +95 -0
- package/skills/estack-leadership-coach/references/hormozi-alex_followthrough.md +152 -0
- package/skills/estack-leadership-coach/references/hormozi-leila_4-stages.md +146 -0
- package/skills/estack-leadership-coach/references/oncken-wass_monkeys-hbr-1974.md +128 -0
- package/skills/estack-leadership-coach/references/sanchez_main-street-millionaire.md +196 -0
- package/skills/estack-leadership-coach/references/sullivan_who-not-how.md +137 -0
- package/skills/estack-leadership-coach/references/van-edwards_cues.md +189 -0
- package/skills/estack-migrate-claude-session-history/SKILL.md +226 -0
- package/skills/estack-migrate-claude-session-history/references/path-encoding.md +55 -0
- package/skills/estack-migrate-claude-session-history/references/troubleshooting.md +96 -0
- package/skills/estack-migrate-claude-session-history/scripts/migrate-claude-history.js +1123 -0
- package/skills/estack-migrate-claude-session-history/scripts/test-append-note.js +48 -0
- package/skills/estack-migrate-claude-session-history/scripts/test-validate-migration.py +326 -0
- package/skills/estack-migrate-claude-session-history/scripts/validate-migration.py +493 -0
- package/skills/estack-pdf-to-md/SKILL.md +180 -0
- package/skills/estack-pdf-to-md/scripts/pdf_to_md.py +596 -0
- package/skills/estack-productivity-prioritization-coach/SKILL.md +124 -0
- package/skills/estack-productivity-prioritization-coach/sources/01-tony-robbins-rpm.md +39 -0
- package/skills/estack-productivity-prioritization-coach/sources/02-justin-sung-task-prioritization.md +34 -0
- package/skills/estack-prompt-builder-coach/SKILL.md +81 -81
- package/skills/estack-prompt-builder-coach/definition-of-done-generator.md +42 -42
- package/skills/estack-prompt-builder-coach/prompt-builder.md +37 -37
- package/skills/estack-prompt-builder-coach/task-shaper.md +36 -36
- package/skills/estack-prompt-builder-coach/vague-ask-auditor.md +37 -37
- package/skills/estack-read-claude-session-history/SKILL.md +204 -204
- package/skills/estack-read-claude-session-history/references/jsonl-schema.md +126 -126
- package/skills/estack-read-claude-session-history/references/modes.md +423 -423
- package/skills/estack-read-claude-session-history/references/recipes.md +271 -271
- package/skills/estack-read-claude-session-history/scripts/lib/__init__.py +1 -1
- package/skills/estack-read-claude-session-history/scripts/lib/parser.py +460 -460
- package/skills/estack-read-claude-session-history/scripts/lib/paths.py +234 -234
- package/skills/estack-read-claude-session-history/scripts/lib/search.py +179 -179
- package/skills/estack-read-claude-session-history/scripts/lib/subagents.py +88 -88
- package/skills/estack-read-claude-session-history/scripts/lib/tools.py +144 -144
- package/skills/estack-read-claude-session-history/scripts/read_transcript.py +1776 -1776
- package/skills/estack-read-claude-session-history/scripts/tests/conftest.py +40 -40
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/README.md +20 -20
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/all-noise.jsonl +4 -4
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/basic-session.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-gaps.jsonl +9 -9
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-noise.jsonl +7 -7
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-a.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-parallel-b.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/engagement-waiting.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/interrupted.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/multi-compact.jsonl +8 -8
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/pending-user.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta/subagents/agent-aaa.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-no-meta.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent/subagents/agent-xyz123.meta.json +1 -1
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/subagent-parent.jsonl +4 -4
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/time-spread.jsonl +6 -6
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/timeline-day-test.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/tool-zoo.jsonl +10 -10
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/truncated.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/unicode.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-advisor.jsonl +3 -3
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-compact.jsonl +5 -5
- package/skills/estack-read-claude-session-history/scripts/tests/fixtures/with-thinking.jsonl +2 -2
- package/skills/estack-read-claude-session-history/scripts/tests/test_backup_roots.py +56 -56
- package/skills/estack-read-claude-session-history/scripts/tests/test_engagement.py +239 -239
- package/skills/estack-read-claude-session-history/scripts/tests/test_json_format.py +201 -201
- package/skills/estack-read-claude-session-history/scripts/tests/test_modes.py +199 -199
- package/skills/estack-read-claude-session-history/scripts/tests/test_parser.py +195 -195
- package/skills/estack-read-claude-session-history/scripts/tests/test_paths.py +133 -133
- package/skills/estack-read-claude-session-history/scripts/tests/test_search.py +78 -78
- package/skills/estack-read-claude-session-history/scripts/tests/test_subagents.py +43 -43
- package/skills/estack-read-claude-session-history/scripts/tests/test_timeline.py +179 -179
- package/skills/estack-read-claude-session-history/scripts/tests/test_timezone_and_project.py +212 -212
- package/skills/estack-read-claude-session-history/scripts/tests/test_tools.py +80 -80
- package/skills/estack-repo-search/SKILL.md +65 -65
- package/skills/estack-vscode-file-recovery/SKILL.md +188 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
const mod = require('./migrate-claude-history.js');
|
|
7
|
+
|
|
8
|
+
// Set up a tiny synthetic .jsonl in a temp dir
|
|
9
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'migrate-test-'));
|
|
10
|
+
const testFile = path.join(tmp, 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jsonl');
|
|
11
|
+
const sampleEntries = [
|
|
12
|
+
{ type: 'permission-mode', permissionMode: 'default', sessionId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' },
|
|
13
|
+
{ type: 'user', message: { role: 'user', content: 'Hello' }, uuid: '11111111-1111-1111-1111-111111111111', parentUuid: null, sessionId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', timestamp: '2026-05-24T18:00:00.000Z', cwd: 'C:\\fake\\old', version: '2.1.0' },
|
|
14
|
+
];
|
|
15
|
+
fs.writeFileSync(testFile, sampleEntries.map((e) => JSON.stringify(e)).join('\n') + '\n', 'utf8');
|
|
16
|
+
|
|
17
|
+
const oldRepo = mod.parseWindowsRepoPath('C:\\fake\\old', 'old');
|
|
18
|
+
const newRepo = mod.parseWindowsRepoPath('C:\\fake\\new', 'new');
|
|
19
|
+
|
|
20
|
+
const summary = { migrationNotesAppended: 0, migrationNotesSkipped: 0 };
|
|
21
|
+
|
|
22
|
+
// First call — should append
|
|
23
|
+
mod.appendMigrationNote({ filePath: testFile, oldRepo, newRepo, dryRun: false, summary });
|
|
24
|
+
console.log('After first call:', summary);
|
|
25
|
+
|
|
26
|
+
// Second call — should detect duplicate and skip
|
|
27
|
+
mod.appendMigrationNote({ filePath: testFile, oldRepo, newRepo, dryRun: false, summary });
|
|
28
|
+
console.log('After second call:', summary);
|
|
29
|
+
|
|
30
|
+
// Inspect the appended entry
|
|
31
|
+
const lines = fs.readFileSync(testFile, 'utf8').split('\n').filter((l) => l.trim());
|
|
32
|
+
const appended = JSON.parse(lines[lines.length - 1]);
|
|
33
|
+
|
|
34
|
+
console.log('');
|
|
35
|
+
console.log('=== Appended entry shape ===');
|
|
36
|
+
console.log('type: ', appended.type);
|
|
37
|
+
console.log('isMeta: ', 'isMeta' in appended ? appended.isMeta : '<not set>');
|
|
38
|
+
console.log('parent: ', appended.parentUuid);
|
|
39
|
+
console.log('uuid: ', appended.uuid);
|
|
40
|
+
console.log('cwd: ', appended.cwd);
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log('=== content (first 200 chars) ===');
|
|
43
|
+
console.log(appended.message.content.slice(0, 200) + '...');
|
|
44
|
+
|
|
45
|
+
// Cleanup
|
|
46
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log('Test passed:', summary.migrationNotesAppended === 1 && summary.migrationNotesSkipped === 1 && !('isMeta' in appended) ? 'YES' : 'NO');
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Self-test for validate-migration.py.
|
|
2
|
+
|
|
3
|
+
Builds synthetic transcripts with known defects, runs each check against
|
|
4
|
+
them, and confirms each check catches what it's supposed to catch (and
|
|
5
|
+
doesn't false-positive on clean inputs).
|
|
6
|
+
|
|
7
|
+
Run from the skill's scripts/ directory:
|
|
8
|
+
python test-validate-migration.py
|
|
9
|
+
|
|
10
|
+
Exit 0 on full pass, 1 if any case fails.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib.util
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# Import validate-migration.py despite the hyphen in the filename.
|
|
24
|
+
SCRIPT_DIR = Path(__file__).parent
|
|
25
|
+
spec = importlib.util.spec_from_file_location(
|
|
26
|
+
"validate_migration",
|
|
27
|
+
SCRIPT_DIR / "validate-migration.py",
|
|
28
|
+
)
|
|
29
|
+
vm = importlib.util.module_from_spec(spec)
|
|
30
|
+
assert spec.loader is not None
|
|
31
|
+
# Register before exec so @dataclass can resolve the module via sys.modules (Python 3.13+).
|
|
32
|
+
sys.modules["validate_migration"] = vm
|
|
33
|
+
spec.loader.exec_module(vm)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
SESSION_ID = "11111111-2222-3333-4444-555555555555"
|
|
37
|
+
OLD_REPO = r"C:\fake\old"
|
|
38
|
+
NEW_REPO = r"C:\fake\new"
|
|
39
|
+
NEW_REPO_SUBDIR = r"C:\fake\old\subproject" # for prefix-containment tests
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def make_clean_entries(
|
|
43
|
+
session_id: str = SESSION_ID,
|
|
44
|
+
new_cwd: str = NEW_REPO,
|
|
45
|
+
with_migration_note: bool = True,
|
|
46
|
+
) -> list[dict]:
|
|
47
|
+
entries = [
|
|
48
|
+
{
|
|
49
|
+
"type": "permission-mode",
|
|
50
|
+
"permissionMode": "default",
|
|
51
|
+
"sessionId": session_id,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"type": "user",
|
|
55
|
+
"message": {"role": "user", "content": "Hello"},
|
|
56
|
+
"uuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
|
57
|
+
"parentUuid": None,
|
|
58
|
+
"sessionId": session_id,
|
|
59
|
+
"cwd": new_cwd,
|
|
60
|
+
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"type": "assistant",
|
|
64
|
+
"message": {"role": "assistant", "content": "Hi back"},
|
|
65
|
+
"uuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
|
|
66
|
+
"parentUuid": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
|
67
|
+
"sessionId": session_id,
|
|
68
|
+
"cwd": new_cwd,
|
|
69
|
+
"timestamp": "2026-01-01T00:00:01.000Z",
|
|
70
|
+
},
|
|
71
|
+
]
|
|
72
|
+
if with_migration_note:
|
|
73
|
+
entries.append({
|
|
74
|
+
"type": "user",
|
|
75
|
+
"message": {
|
|
76
|
+
"role": "user",
|
|
77
|
+
"content": "<session-migration-note>\nMigrated from x to y.\n</session-migration-note>",
|
|
78
|
+
},
|
|
79
|
+
"uuid": "cccccccc-cccc-cccc-cccc-cccccccccccc",
|
|
80
|
+
"parentUuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
|
|
81
|
+
"sessionId": session_id,
|
|
82
|
+
"cwd": new_cwd,
|
|
83
|
+
"timestamp": "2026-01-01T00:00:02.000Z",
|
|
84
|
+
})
|
|
85
|
+
return entries
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def write_jsonl(path: Path, entries: list[dict], raw_lines: list[str] | None = None) -> None:
|
|
89
|
+
if raw_lines is not None:
|
|
90
|
+
path.write_text("\n".join(raw_lines) + "\n", encoding="utf-8")
|
|
91
|
+
else:
|
|
92
|
+
path.write_text(
|
|
93
|
+
"\n".join(json.dumps(e) for e in entries) + "\n",
|
|
94
|
+
encoding="utf-8",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Track results
|
|
99
|
+
results: list[tuple[str, bool, str]] = []
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def record(test_name: str, expected_pass: bool, result_obj, *, expect_detail_contains: str | None = None) -> None:
|
|
103
|
+
matches_expected = result_obj.passed == expected_pass
|
|
104
|
+
detail_ok = True
|
|
105
|
+
if expect_detail_contains and not matches_expected:
|
|
106
|
+
# If the check unexpectedly went the wrong way, don't penalize detail.
|
|
107
|
+
pass
|
|
108
|
+
elif expect_detail_contains:
|
|
109
|
+
detail_ok = expect_detail_contains.lower() in result_obj.detail.lower()
|
|
110
|
+
ok = matches_expected and detail_ok
|
|
111
|
+
results.append((test_name, ok, result_obj.detail))
|
|
112
|
+
label = "PASS" if ok else "FAIL"
|
|
113
|
+
expected = "should PASS" if expected_pass else "should FAIL"
|
|
114
|
+
print(f"[{label}] {test_name:<60s} ({expected}; got {'PASS' if result_obj.passed else 'FAIL'}) {result_obj.detail}")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main() -> int:
|
|
118
|
+
with tempfile.TemporaryDirectory() as tmp_str:
|
|
119
|
+
tmp = Path(tmp_str)
|
|
120
|
+
|
|
121
|
+
# --- HAPPY PATH: a clean synthetic transcript ---
|
|
122
|
+
clean_entries = make_clean_entries()
|
|
123
|
+
clean_file = tmp / f"{SESSION_ID}.jsonl"
|
|
124
|
+
write_jsonl(clean_file, clean_entries)
|
|
125
|
+
|
|
126
|
+
print("\n--- Happy path: all checks should PASS ---")
|
|
127
|
+
record("happy.parse_integrity", True, vm.check_parse_integrity(clean_file))
|
|
128
|
+
record("happy.schema", True, vm.check_schema(clean_entries))
|
|
129
|
+
record("happy.session_id", True, vm.check_session_id_consistency(clean_entries, SESSION_ID))
|
|
130
|
+
record("happy.parent_chain", True, vm.check_parent_uuid_chains(clean_entries))
|
|
131
|
+
record("happy.cwd", True, vm.check_cwd_consistency(clean_entries, NEW_REPO))
|
|
132
|
+
record("happy.migration_note", True, vm.check_migration_note(clean_entries))
|
|
133
|
+
record("happy.stale_refs", True, vm.check_stale_path_references(clean_entries, OLD_REPO, NEW_REPO))
|
|
134
|
+
record("happy.sidecar", True, vm.check_sidecar_integrity(clean_file, SESSION_ID))
|
|
135
|
+
|
|
136
|
+
# --- Failure cases: one per check ---
|
|
137
|
+
print("\n--- Failure cases: each should be detected ---")
|
|
138
|
+
|
|
139
|
+
# 1. Malformed JSON line
|
|
140
|
+
bad_parse_file = tmp / "bad_parse.jsonl"
|
|
141
|
+
write_jsonl(
|
|
142
|
+
bad_parse_file,
|
|
143
|
+
[],
|
|
144
|
+
raw_lines=[json.dumps(clean_entries[0]), "{not valid json", json.dumps(clean_entries[1])],
|
|
145
|
+
)
|
|
146
|
+
record("fail.parse_integrity", False, vm.check_parse_integrity(bad_parse_file))
|
|
147
|
+
|
|
148
|
+
# 2. Schema violation: user entry with malformed message
|
|
149
|
+
bad_schema = list(clean_entries)
|
|
150
|
+
bad_schema[1] = {**clean_entries[1], "message": "not-a-dict"}
|
|
151
|
+
record("fail.schema (bad message)", False, vm.check_schema(bad_schema))
|
|
152
|
+
|
|
153
|
+
# 3. Schema violation: bad uuid format
|
|
154
|
+
bad_uuid = list(clean_entries)
|
|
155
|
+
bad_uuid[1] = {**clean_entries[1], "uuid": "not-a-uuid"}
|
|
156
|
+
record("fail.schema (bad uuid)", False, vm.check_schema(bad_uuid))
|
|
157
|
+
|
|
158
|
+
# 4. SessionId inconsistency
|
|
159
|
+
mixed_sids = list(clean_entries)
|
|
160
|
+
mixed_sids[2] = {**clean_entries[2], "sessionId": "00000000-0000-0000-0000-000000000000"}
|
|
161
|
+
record("fail.session_id (mixed sids)", False, vm.check_session_id_consistency(mixed_sids, SESSION_ID))
|
|
162
|
+
|
|
163
|
+
# 5. Broken parent uuid chain
|
|
164
|
+
broken_parent = list(clean_entries)
|
|
165
|
+
broken_parent[2] = {**clean_entries[2], "parentUuid": "99999999-9999-9999-9999-999999999999"}
|
|
166
|
+
record("fail.parent_chain (orphan parent)", False, vm.check_parent_uuid_chains(broken_parent))
|
|
167
|
+
|
|
168
|
+
# 6. Multiple distinct cwd values
|
|
169
|
+
mixed_cwd = list(clean_entries)
|
|
170
|
+
mixed_cwd[2] = {**clean_entries[2], "cwd": r"C:\different\cwd"}
|
|
171
|
+
record("fail.cwd (multiple distinct)", False, vm.check_cwd_consistency(mixed_cwd, NEW_REPO))
|
|
172
|
+
|
|
173
|
+
# 7. Cwd doesn't match expected new_repo
|
|
174
|
+
wrong_new_cwd = make_clean_entries(new_cwd=r"C:\unexpected\path")
|
|
175
|
+
record("fail.cwd (wrong new_repo)", False, vm.check_cwd_consistency(wrong_new_cwd, NEW_REPO))
|
|
176
|
+
|
|
177
|
+
# 8. No migration note
|
|
178
|
+
no_note = make_clean_entries(with_migration_note=False)
|
|
179
|
+
record("fail.migration_note (missing)", False, vm.check_migration_note(no_note))
|
|
180
|
+
|
|
181
|
+
# 9. Multiple migration notes (duplicate append)
|
|
182
|
+
dup_note = list(clean_entries) + [{
|
|
183
|
+
"type": "user",
|
|
184
|
+
"message": {"role": "user", "content": "<session-migration-note>\nduplicate\n</session-migration-note>"},
|
|
185
|
+
"uuid": "dddddddd-dddd-dddd-dddd-dddddddddddd",
|
|
186
|
+
"parentUuid": "cccccccc-cccc-cccc-cccc-cccccccccccc",
|
|
187
|
+
"sessionId": SESSION_ID,
|
|
188
|
+
"cwd": NEW_REPO,
|
|
189
|
+
"timestamp": "2026-01-01T00:00:03.000Z",
|
|
190
|
+
}]
|
|
191
|
+
record("fail.migration_note (duplicate)", False, vm.check_migration_note(dup_note))
|
|
192
|
+
|
|
193
|
+
# 10. Migration note with isMeta=True (should be regular user message)
|
|
194
|
+
meta_note = make_clean_entries(with_migration_note=False) + [{
|
|
195
|
+
"type": "user",
|
|
196
|
+
"message": {"role": "user", "content": "<session-migration-note>\nmeta version\n</session-migration-note>"},
|
|
197
|
+
"isMeta": True,
|
|
198
|
+
"uuid": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee",
|
|
199
|
+
"parentUuid": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
|
|
200
|
+
"sessionId": SESSION_ID,
|
|
201
|
+
"cwd": NEW_REPO,
|
|
202
|
+
}]
|
|
203
|
+
record("fail.migration_note (isMeta=true)", False, vm.check_migration_note(meta_note))
|
|
204
|
+
|
|
205
|
+
# 11. Truly-stale old path in pre-note entry (unrelated old vs new)
|
|
206
|
+
stale_entries = make_clean_entries(new_cwd=NEW_REPO)
|
|
207
|
+
stale_entries[1] = {
|
|
208
|
+
**stale_entries[1],
|
|
209
|
+
"message": {
|
|
210
|
+
"role": "user",
|
|
211
|
+
"content": f"Check the file at {OLD_REPO}\\stuff.txt please",
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
record("fail.stale_refs (unrelated paths)", False, vm.check_stale_path_references(stale_entries, OLD_REPO, NEW_REPO))
|
|
215
|
+
|
|
216
|
+
# 12. Prefix-containment false-positive shouldn't trigger
|
|
217
|
+
# When new_repo is a subdir of old_repo, references to old-path-as-prefix-of-new-path are NOT stale.
|
|
218
|
+
prefix_clean = make_clean_entries(new_cwd=NEW_REPO_SUBDIR)
|
|
219
|
+
record(
|
|
220
|
+
"happy.stale_refs (subdir new path, no actual stale refs)",
|
|
221
|
+
True,
|
|
222
|
+
vm.check_stale_path_references(prefix_clean, OLD_REPO, NEW_REPO_SUBDIR),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# 13. Post-note stale refs should NOT trigger (out of scope)
|
|
226
|
+
post_note_stale = list(clean_entries) + [{
|
|
227
|
+
"type": "tool_result",
|
|
228
|
+
"uuid": "ffffffff-ffff-ffff-ffff-ffffffffffff",
|
|
229
|
+
"parentUuid": "cccccccc-cccc-cccc-cccc-cccccccccccc",
|
|
230
|
+
"sessionId": SESSION_ID,
|
|
231
|
+
"cwd": NEW_REPO,
|
|
232
|
+
"message": {
|
|
233
|
+
"role": "user",
|
|
234
|
+
"content": f"Tool output mentioning {OLD_REPO}\\somefile.txt — but this is AFTER migration",
|
|
235
|
+
},
|
|
236
|
+
}]
|
|
237
|
+
record(
|
|
238
|
+
"happy.stale_refs (post-note refs ignored)",
|
|
239
|
+
True,
|
|
240
|
+
vm.check_stale_path_references(post_note_stale, OLD_REPO, NEW_REPO),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# 14. Sidecar with parse error
|
|
244
|
+
sidecar_file = tmp / SESSION_ID
|
|
245
|
+
sidecar_file.mkdir()
|
|
246
|
+
(sidecar_file / "agent-test.jsonl").write_text(
|
|
247
|
+
json.dumps({"type": "user", "sessionId": SESSION_ID, "uuid": "11111111-1111-1111-1111-111111111111"}) + "\n{bad json line\n",
|
|
248
|
+
encoding="utf-8",
|
|
249
|
+
)
|
|
250
|
+
record("fail.sidecar (bad parse in subagent)", False, vm.check_sidecar_integrity(clean_file, SESSION_ID))
|
|
251
|
+
|
|
252
|
+
# 15. Sidecar with wrong sessionId
|
|
253
|
+
shutil.rmtree(sidecar_file)
|
|
254
|
+
sidecar_file.mkdir()
|
|
255
|
+
(sidecar_file / "agent-mismatch.jsonl").write_text(
|
|
256
|
+
json.dumps({"type": "user", "sessionId": "different-session-id", "uuid": "11111111-1111-1111-1111-111111111111"}) + "\n",
|
|
257
|
+
encoding="utf-8",
|
|
258
|
+
)
|
|
259
|
+
record("fail.sidecar (wrong sessionId)", False, vm.check_sidecar_integrity(clean_file, SESSION_ID))
|
|
260
|
+
|
|
261
|
+
# 16. Sidecar clean
|
|
262
|
+
shutil.rmtree(sidecar_file)
|
|
263
|
+
sidecar_file.mkdir()
|
|
264
|
+
(sidecar_file / "agent-good.jsonl").write_text(
|
|
265
|
+
json.dumps({"type": "user", "sessionId": SESSION_ID, "uuid": "11111111-1111-1111-1111-111111111111"}) + "\n",
|
|
266
|
+
encoding="utf-8",
|
|
267
|
+
)
|
|
268
|
+
record("happy.sidecar (clean)", True, vm.check_sidecar_integrity(clean_file, SESSION_ID))
|
|
269
|
+
|
|
270
|
+
# 17. Backup cross-validation happy path
|
|
271
|
+
backup_file = tmp / f"backup-{SESSION_ID}.jsonl"
|
|
272
|
+
# Source backup = clean entries WITHOUT the migration note (note is added by migration)
|
|
273
|
+
source_entries = make_clean_entries(with_migration_note=False)
|
|
274
|
+
write_jsonl(backup_file, source_entries)
|
|
275
|
+
live_entries = make_clean_entries(with_migration_note=True) # = source + note
|
|
276
|
+
# Sidecar live present + matching backup absent (skipped sub-check)
|
|
277
|
+
result = vm.check_backup_cross_validation(
|
|
278
|
+
migrated_entries=live_entries,
|
|
279
|
+
source_backup_path=backup_file,
|
|
280
|
+
sidecar_live=tmp / "no-sidecar-live", # doesn't exist; counts as 0
|
|
281
|
+
sidecar_backup=None,
|
|
282
|
+
target_backup_dir=None,
|
|
283
|
+
target_live_dir=tmp,
|
|
284
|
+
)
|
|
285
|
+
record("happy.backup_cross_validation", True, result)
|
|
286
|
+
|
|
287
|
+
# 18. Backup cross-validation: entry count mismatch
|
|
288
|
+
truncated = make_clean_entries(with_migration_note=True)[:-2] # drop note + one entry
|
|
289
|
+
result = vm.check_backup_cross_validation(
|
|
290
|
+
migrated_entries=truncated,
|
|
291
|
+
source_backup_path=backup_file,
|
|
292
|
+
sidecar_live=tmp / "no-sidecar-live",
|
|
293
|
+
sidecar_backup=None,
|
|
294
|
+
target_backup_dir=None,
|
|
295
|
+
target_live_dir=tmp,
|
|
296
|
+
)
|
|
297
|
+
record("fail.backup_cross_validation (entry count)", False, result)
|
|
298
|
+
|
|
299
|
+
# 19. Backup cross-validation: uuid order broken
|
|
300
|
+
reordered = list(live_entries)
|
|
301
|
+
reordered[1], reordered[2] = reordered[2], reordered[1]
|
|
302
|
+
result = vm.check_backup_cross_validation(
|
|
303
|
+
migrated_entries=reordered,
|
|
304
|
+
source_backup_path=backup_file,
|
|
305
|
+
sidecar_live=tmp / "no-sidecar-live",
|
|
306
|
+
sidecar_backup=None,
|
|
307
|
+
target_backup_dir=None,
|
|
308
|
+
target_live_dir=tmp,
|
|
309
|
+
)
|
|
310
|
+
record("fail.backup_cross_validation (uuid order)", False, result)
|
|
311
|
+
|
|
312
|
+
# --- Summary ---
|
|
313
|
+
passed = sum(1 for _, ok, _ in results if ok)
|
|
314
|
+
total = len(results)
|
|
315
|
+
print(f"\n=== test-validate-migration: {passed}/{total} cases passed ===")
|
|
316
|
+
if passed != total:
|
|
317
|
+
print("\nFailing cases:")
|
|
318
|
+
for name, ok, detail in results:
|
|
319
|
+
if not ok:
|
|
320
|
+
print(f" - {name}: {detail}")
|
|
321
|
+
return 1
|
|
322
|
+
return 0
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
if __name__ == "__main__":
|
|
326
|
+
sys.exit(main())
|