claude-dev-env 1.93.0 → 1.94.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/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/audit-contract.md +1 -1
- package/_shared/pr-loop/gh-payloads.md +3 -3
- package/_shared/pr-loop/post-audit-thread-contract.md +51 -0
- package/_shared/pr-loop/scripts/README.md +2 -2
- package/_shared/pr-loop/scripts/code_rules_gate.py +127 -13
- package/_shared/pr-loop/scripts/fix_hookspath.py +47 -12
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +4 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -69
- package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -374
- package/_shared/pr-loop/state-schema.md +3 -22
- package/bin/CLAUDE.md +1 -1
- package/bin/install.mjs +2 -7
- package/bin/install.test.mjs +10 -4
- package/hooks/blocking/CLAUDE.md +2 -0
- package/hooks/blocking/pii_commit_command.py +398 -0
- package/hooks/blocking/pii_payload_scan.py +133 -0
- package/hooks/blocking/pii_prevention_blocker.py +151 -449
- package/hooks/blocking/pii_scanner.py +26 -1
- package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +2 -2
- package/hooks/blocking/test_pii_prevention_blocker.py +299 -0
- package/hooks/blocking/test_pii_scanner.py +27 -2
- package/hooks/blocking/test_verdict_directory_write_blocker.py +42 -7
- package/hooks/hooks_constants/CLAUDE.md +2 -2
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -2
- package/hooks/hooks_constants/local_identity.py +65 -15
- package/hooks/hooks_constants/pii_prevention_constants.py +20 -8
- package/hooks/hooks_constants/pr_description_enforcer_constants.py +1 -1
- package/hooks/hooks_constants/test_local_identity.py +68 -0
- package/package.json +1 -1
- package/scripts/CLAUDE.md +10 -1
- package/scripts/Get-SessionAccount.ps1 +210 -0
- package/scripts/tests/CLAUDE.md +15 -1
- package/scripts/tests/Get-SessionAccount.Tests.ps1 +350 -0
- package/skills/CLAUDE.md +3 -25
- package/skills/anthropic-plan/SKILL.md +1 -13
- package/skills/autoconverge/CLAUDE.md +5 -2
- package/skills/autoconverge/SKILL.md +112 -387
- package/skills/autoconverge/reference/CLAUDE.md +4 -1
- package/skills/autoconverge/reference/closing-report.md +9 -2
- package/skills/autoconverge/reference/convergence.md +26 -22
- package/skills/autoconverge/reference/headless-safety.md +44 -0
- package/skills/autoconverge/reference/multi-pr.md +88 -0
- package/skills/autoconverge/reference/self-closing-loop.md +84 -0
- package/skills/autoconverge/reference/stop-conditions.md +21 -13
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +87 -27
- package/skills/autoconverge/workflow/converge.contract.test.mjs +40 -10
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +179 -47
- package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.mjs +284 -94
- package/skills/bugteam/CLAUDE.md +1 -1
- package/skills/bugteam/CONSTRAINTS.md +3 -3
- package/skills/bugteam/PROMPTS.md +8 -19
- package/skills/bugteam/SKILL.md +13 -14
- package/skills/bugteam/reference/CLAUDE.md +1 -1
- package/skills/bugteam/reference/audit-and-teammates.md +2 -2
- package/skills/bugteam/reference/copilot-gap-analysis.md +2 -0
- package/skills/bugteam/reference/github-pr-reviews.md +56 -38
- package/skills/bugteam/reference/team-setup.md +11 -11
- package/skills/bugteam/scripts/CLAUDE.md +6 -6
- package/skills/bugteam/scripts/README.md +20 -8
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +13 -1463
- package/skills/bugteam/scripts/bugteam_fix_hookspath.py +23 -358
- package/skills/bugteam/scripts/bugteam_preflight.py +13 -315
- package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +2 -3
- package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -1053
- package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +30 -347
- package/skills/bugteam/scripts/test_bugteam_preflight.py +30 -305
- package/skills/copilot-finding-triage/SKILL.md +80 -28
- package/skills/copilot-finding-triage/reference/tier-rubric.md +70 -11
- package/skills/copilot-finding-triage/templates/notification.md +8 -3
- package/skills/everything-search/SKILL.md +1 -2
- package/skills/grokify/SKILL.md +58 -0
- package/skills/grokify/templates/handoff-template.md +92 -0
- package/skills/orchestrator/SKILL.md +12 -8
- package/skills/orchestrator-refresh/SKILL.md +3 -3
- package/skills/pr-converge/SKILL.md +132 -233
- package/skills/pr-converge/reference/convergence-gates.md +102 -66
- package/skills/pr-converge/reference/examples.md +37 -25
- package/skills/pr-converge/reference/fix-protocol.md +7 -7
- package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -4
- package/skills/pr-converge/reference/per-tick.md +64 -41
- package/skills/pr-converge/reference/state-schema.md +27 -2
- package/skills/pr-converge/scripts/CLAUDE.md +8 -0
- package/skills/pr-converge/scripts/README.md +4 -3
- package/skills/pr-converge/scripts/_pr_converge_path_setup.py +23 -0
- package/skills/pr-converge/scripts/check_convergence.py +189 -645
- package/skills/pr-converge/scripts/check_convergence_gates.py +235 -0
- package/skills/pr-converge/scripts/check_convergence_thread_gates.py +219 -0
- package/skills/pr-converge/scripts/conftest.py +8 -0
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -0
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -0
- package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +26 -0
- package/skills/pr-converge/scripts/test_check_convergence.py +73 -228
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +321 -0
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +68 -0
- package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +75 -0
- package/skills/privacy-hygiene/SKILL.md +8 -7
- package/skills/reviewer-gates/SKILL.md +26 -8
- package/skills/session-log/SKILL.md +11 -12
- package/skills/session-tidy/SKILL.md +40 -30
- package/skills/skill-builder/CLAUDE.md +13 -10
- package/skills/skill-builder/SKILL.md +31 -31
- package/skills/skill-builder/references/CLAUDE.md +10 -6
- package/skills/skill-builder/references/delegation-map.md +13 -4
- package/skills/skill-builder/references/description-field.md +113 -0
- package/skills/skill-builder/references/self-audit-checklist.md +21 -4
- package/skills/skill-builder/references/skill-modularity.md +113 -0
- package/skills/skill-builder/references/skill-types.md +4 -0
- package/skills/skill-builder/templates/CLAUDE.md +4 -4
- package/skills/skill-builder/templates/gap-analysis.md +25 -1
- package/skills/skill-builder/workflows/CLAUDE.md +6 -4
- package/skills/skill-builder/workflows/improve-skill.md +17 -16
- package/skills/skill-builder/workflows/new-skill.md +34 -10
- package/skills/skill-builder/workflows/polish-skill.md +59 -43
- package/skills/usage-pause/SKILL.md +10 -14
- package/skills/bdd-protocol/CLAUDE.md +0 -26
- package/skills/bdd-protocol/SKILL.md +0 -30
- package/skills/bdd-protocol/references/CLAUDE.md +0 -10
- package/skills/bdd-protocol/references/anti-patterns.md +0 -26
- package/skills/bdd-protocol/references/example-mapping.md +0 -23
- package/skills/bg-agent/CLAUDE.md +0 -17
- package/skills/bg-agent/SKILL.md +0 -69
- package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +0 -55
- package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_fix_hookspath_constants.py +0 -26
- package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_preflight_constants.py +0 -35
- package/skills/caveman/CLAUDE.md +0 -15
- package/skills/caveman/SKILL.md +0 -38
- package/skills/code/CLAUDE.md +0 -17
- package/skills/code/SKILL.md +0 -178
- package/skills/deep-research/CLAUDE.md +0 -17
- package/skills/deep-research/SKILL.md +0 -80
- package/skills/findbugs/CLAUDE.md +0 -20
- package/skills/findbugs/SKILL.md +0 -241
- package/skills/fixbugs/CLAUDE.md +0 -19
- package/skills/fixbugs/SKILL.md +0 -142
- package/skills/gh-paginate/CLAUDE.md +0 -18
- package/skills/gh-paginate/SKILL.md +0 -84
- package/skills/gotcha/CLAUDE.md +0 -33
- package/skills/gotcha/SKILL.md +0 -73
- package/skills/implement/CLAUDE.md +0 -27
- package/skills/implement/SKILL.md +0 -66
- package/skills/implement/scripts/CLAUDE.md +0 -22
- package/skills/implement/scripts/append_note.py +0 -133
- package/skills/implement/scripts/implement_scripts_constants/CLAUDE.md +0 -22
- package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
- package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +0 -12
- package/skills/implement/scripts/test_append_note.py +0 -191
- package/skills/log-audit/CLAUDE.md +0 -20
- package/skills/log-audit/SKILL.md +0 -66
- package/skills/log-audit/reference/CLAUDE.md +0 -9
- package/skills/log-audit/reference/charter.md +0 -52
- package/skills/log-audit/scripts/CLAUDE.md +0 -27
- package/skills/log-audit/scripts/cluster_recurrences.py +0 -261
- package/skills/log-audit/scripts/collect_log_window.py +0 -199
- package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +0 -12
- package/skills/log-audit/scripts/log_audit_constants/__init__.py +0 -0
- package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +0 -23
- package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +0 -24
- package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +0 -49
- package/skills/log-audit/scripts/mine_copilot_findings.py +0 -302
- package/skills/log-audit/scripts/test_cluster_recurrences.py +0 -160
- package/skills/log-audit/scripts/test_collect_log_window.py +0 -111
- package/skills/log-audit/scripts/test_mine_copilot_findings.py +0 -126
- package/skills/logifix/CLAUDE.md +0 -36
- package/skills/logifix/SKILL.md +0 -69
- package/skills/logifix/scripts/CLAUDE.md +0 -16
- package/skills/logifix/scripts/logifix.ps1 +0 -205
- package/skills/monitor-open-prs/CLAUDE.md +0 -34
- package/skills/monitor-open-prs/SKILL.md +0 -88
- package/skills/monitor-open-prs/scripts/CLAUDE.md +0 -17
- package/skills/monitor-open-prs/scripts/discover_open_prs.py +0 -69
- package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +0 -149
- package/skills/monitor-open-prs/test_skill_contract.py +0 -27
- package/skills/post-audit-findings/SKILL.md +0 -80
- package/skills/pr-consistency-audit/CLAUDE.md +0 -34
- package/skills/pr-consistency-audit/SKILL.md +0 -116
- package/skills/pr-consistency-audit/reference/CLAUDE.md +0 -16
- package/skills/pr-consistency-audit/reference/detection-rules.md +0 -96
- package/skills/pr-consistency-audit/reference/illustrations.md +0 -78
- package/skills/pr-fix-protocol/SKILL.md +0 -65
- package/skills/pr-review-responder/CLAUDE.md +0 -35
- package/skills/pr-review-responder/EXAMPLES.md +0 -590
- package/skills/pr-review-responder/PRINCIPLES.md +0 -533
- package/skills/pr-review-responder/README.md +0 -168
- package/skills/pr-review-responder/SKILL.md +0 -203
- package/skills/pr-review-responder/TESTING.md +0 -350
- package/skills/pr-scope-resolve/SKILL.md +0 -45
- package/skills/pre-compact/CLAUDE.md +0 -24
- package/skills/pre-compact/SKILL.md +0 -134
- package/skills/qbug/CLAUDE.md +0 -40
- package/skills/qbug/SKILL.md +0 -387
- package/skills/qbug/test_qbug_skill_audit_schema.py +0 -152
- package/skills/qbug/test_qbug_skill_post_fix_audit.py +0 -103
- package/skills/refine/CLAUDE.md +0 -44
- package/skills/refine/SKILL.md +0 -262
- package/skills/refine/templates/CLAUDE.md +0 -17
- package/skills/refine/templates/implementation-notes-template.html +0 -56
- package/skills/refine/templates/plan-template.md +0 -60
- package/skills/research-mode/CLAUDE.md +0 -35
- package/skills/research-mode/SKILL.md +0 -53
- package/skills/structure-prompt/CLAUDE.md +0 -42
- package/skills/structure-prompt/SKILL.md +0 -44
- package/skills/structure-prompt/reference/CLAUDE.md +0 -28
- package/skills/structure-prompt/reference/adversarial-tuning.md +0 -62
- package/skills/structure-prompt/reference/block-classification.md +0 -27
- package/skills/structure-prompt/reference/canonical-case.md +0 -48
- package/skills/structure-prompt/reference/citation-depth.md +0 -70
- package/skills/structure-prompt/reference/cleanup.md +0 -33
- package/skills/structure-prompt/reference/constraints.md +0 -33
- package/skills/structure-prompt/reference/directives.md +0 -37
- package/skills/structure-prompt/reference/examples.md +0 -72
- package/skills/structure-prompt/reference/instantiation.md +0 -51
- package/skills/structure-prompt/reference/output-contract.md +0 -72
- package/skills/structure-prompt/reference/per-category.md +0 -23
- package/skills/structure-prompt/reference/persona.md +0 -38
- package/skills/structure-prompt/reference/research.md +0 -33
- package/skills/structure-prompt/reference/structure.md +0 -28
- package/skills/verified-build/CLAUDE.md +0 -33
- package/skills/verified-build/SKILL.md +0 -33
|
@@ -121,6 +121,7 @@ DEFAULT_READABILITY_THRESHOLDS: ReadabilityThresholds = ReadabilityThresholds(
|
|
|
121
121
|
__all__ = [
|
|
122
122
|
"ALL_HEAVY_OPENING_HEADERS",
|
|
123
123
|
"ALL_HEAVY_TESTING_HEADERS",
|
|
124
|
+
"ALL_PR_NUMBER_BEARING_GH_PR_SUBCOMMANDS",
|
|
124
125
|
"ALL_READABILITY_CLI_FLAG_TOKENS",
|
|
125
126
|
"ATOMIC_WRITE_TEMP_SUFFIX",
|
|
126
127
|
"BLOCKQUOTE_LINE_PATTERN",
|
|
@@ -135,7 +136,6 @@ __all__ = [
|
|
|
135
136
|
"FLESCH_SYLLABLES_PER_WORD_COEFFICIENT",
|
|
136
137
|
"FLESCH_WORDS_PER_SENTENCE_COEFFICIENT",
|
|
137
138
|
"GH_PR_COMMAND_MIN_TOKEN_COUNT",
|
|
138
|
-
"ALL_PR_NUMBER_BEARING_GH_PR_SUBCOMMANDS",
|
|
139
139
|
"HEADING_LINE_PATTERN",
|
|
140
140
|
"HEAVY_MIN_BODY_CHARS_FOR_CLASSIFICATION",
|
|
141
141
|
"HEAVY_SHAPE",
|
|
@@ -66,6 +66,74 @@ class TestNasValuesPlaceholderDefault:
|
|
|
66
66
|
assert local_identity.nas_ssh_port() == 22
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
class TestPiiExemptRepositorySlugs:
|
|
70
|
+
def should_read_slugs_from_the_environment_lowercased(self) -> None:
|
|
71
|
+
with patch.dict(
|
|
72
|
+
os.environ,
|
|
73
|
+
{"CLAUDE_PII_EXEMPT_REPOS": "ExemptOwner/exempt-repo, Other/Repo"},
|
|
74
|
+
clear=False,
|
|
75
|
+
):
|
|
76
|
+
slugs = local_identity.pii_exempt_repository_slugs()
|
|
77
|
+
assert slugs == frozenset({"exemptowner/exempt-repo", "other/repo"})
|
|
78
|
+
|
|
79
|
+
def should_read_slugs_from_the_local_identity_file(self, tmp_path: Path) -> None:
|
|
80
|
+
claude_home = tmp_path / ".claude"
|
|
81
|
+
claude_home.mkdir()
|
|
82
|
+
(claude_home / "local-identity.json").write_text(
|
|
83
|
+
json.dumps({"pii_exempt_repositories": ["FileOwner/file-repo"]}),
|
|
84
|
+
encoding="utf-8",
|
|
85
|
+
)
|
|
86
|
+
with patch.dict(os.environ, {}, clear=False):
|
|
87
|
+
os.environ.pop("CLAUDE_PII_EXEMPT_REPOS", None)
|
|
88
|
+
with patch.object(Path, "home", return_value=tmp_path):
|
|
89
|
+
slugs = local_identity.pii_exempt_repository_slugs()
|
|
90
|
+
assert slugs == frozenset({"fileowner/file-repo"})
|
|
91
|
+
|
|
92
|
+
def should_return_an_empty_set_when_no_env_and_no_file(
|
|
93
|
+
self, tmp_path: Path
|
|
94
|
+
) -> None:
|
|
95
|
+
with patch.dict(os.environ, {}, clear=False):
|
|
96
|
+
os.environ.pop("CLAUDE_PII_EXEMPT_REPOS", None)
|
|
97
|
+
with patch.object(Path, "home", return_value=tmp_path):
|
|
98
|
+
assert local_identity.pii_exempt_repository_slugs() == frozenset()
|
|
99
|
+
|
|
100
|
+
def should_prefer_environment_slugs_over_local_identity_file(
|
|
101
|
+
self, tmp_path: Path
|
|
102
|
+
) -> None:
|
|
103
|
+
claude_home = tmp_path / ".claude"
|
|
104
|
+
claude_home.mkdir()
|
|
105
|
+
(claude_home / "local-identity.json").write_text(
|
|
106
|
+
json.dumps({"pii_exempt_repositories": ["FileOwner/file-repo"]}),
|
|
107
|
+
encoding="utf-8",
|
|
108
|
+
)
|
|
109
|
+
with patch.dict(
|
|
110
|
+
os.environ,
|
|
111
|
+
{"CLAUDE_PII_EXEMPT_REPOS": "EnvOwner/env-repo"},
|
|
112
|
+
clear=False,
|
|
113
|
+
):
|
|
114
|
+
with patch.object(Path, "home", return_value=tmp_path):
|
|
115
|
+
slugs = local_identity.pii_exempt_repository_slugs()
|
|
116
|
+
assert slugs == frozenset({"envowner/env-repo"})
|
|
117
|
+
|
|
118
|
+
def should_fall_through_to_json_when_environment_is_whitespace_only(
|
|
119
|
+
self, tmp_path: Path
|
|
120
|
+
) -> None:
|
|
121
|
+
claude_home = tmp_path / ".claude"
|
|
122
|
+
claude_home.mkdir()
|
|
123
|
+
(claude_home / "local-identity.json").write_text(
|
|
124
|
+
json.dumps({"pii_exempt_repositories": ["FileOwner/file-repo"]}),
|
|
125
|
+
encoding="utf-8",
|
|
126
|
+
)
|
|
127
|
+
with patch.dict(
|
|
128
|
+
os.environ,
|
|
129
|
+
{"CLAUDE_PII_EXEMPT_REPOS": " , , "},
|
|
130
|
+
clear=False,
|
|
131
|
+
):
|
|
132
|
+
with patch.object(Path, "home", return_value=tmp_path):
|
|
133
|
+
slugs = local_identity.pii_exempt_repository_slugs()
|
|
134
|
+
assert slugs == frozenset({"fileowner/file-repo"})
|
|
135
|
+
|
|
136
|
+
|
|
69
137
|
class TestDenyMessagesQuoteTheResolvedHost:
|
|
70
138
|
def should_include_the_resolved_host_and_port_in_the_bare_binary_message(
|
|
71
139
|
self,
|
package/package.json
CHANGED
package/scripts/CLAUDE.md
CHANGED
|
@@ -21,6 +21,7 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
21
21
|
| `Install-SweepEmptyDirs.ps1` | Registers `sweep_empty_dirs.py` as a scheduled task on Windows |
|
|
22
22
|
| `check.ps1` | Runs the full code-quality check suite |
|
|
23
23
|
| `Show-Asset.ps1` | Opens files on screen, sizing each image window to the image's pixel dimensions (scaled to fit the screen); non-image files open in their default application |
|
|
24
|
+
| `Get-SessionAccount.ps1` | Reports which Claude account the current session is actually logged into by comparing `~/.claude.json`'s CLI login against a `CLAUDE_USER_DATA_DIR` desktop profile's `lastKnownAccountUuid`, recovering the desktop account's email from profile storage when the two accounts differ |
|
|
24
25
|
|
|
25
26
|
## Subdirectories
|
|
26
27
|
|
|
@@ -28,10 +29,18 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
|
|
|
28
29
|
|---|---|
|
|
29
30
|
| `dev_env_scripts_constants/` | Named constants (`timing.py`) for scripts in this directory |
|
|
30
31
|
| `sync_to_cursor/` | Package that builds Cursor `.mdc` files from Claude rules and docs |
|
|
31
|
-
| `tests/` | pytest suite for the Python scripts in this directory |
|
|
32
|
+
| `tests/` | pytest suite for the Python scripts and Pester (`*.Tests.ps1`) suite for the PowerShell scripts in this directory |
|
|
32
33
|
|
|
33
34
|
## Running tests
|
|
34
35
|
|
|
36
|
+
Python scripts (pytest):
|
|
37
|
+
|
|
35
38
|
```bash
|
|
36
39
|
python -m pytest packages/claude-dev-env/scripts/tests/
|
|
37
40
|
```
|
|
41
|
+
|
|
42
|
+
PowerShell scripts (Pester 5+, `*.Tests.ps1`):
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
Invoke-Pester -Path packages/claude-dev-env/scripts/tests/
|
|
46
|
+
```
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env pwsh
|
|
2
|
+
[CmdletBinding()]
|
|
3
|
+
param(
|
|
4
|
+
[switch]$AsJson
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
$noiseEmailPatterns = @(
|
|
8
|
+
'@example\.(com|org|net)$',
|
|
9
|
+
'(@|\.)sentry\.io$',
|
|
10
|
+
'@anthropic\.',
|
|
11
|
+
'^Claude@\d'
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
$isoLatin1CodePage = 28591
|
|
15
|
+
|
|
16
|
+
$profileStorageDirectoryNames = @('sentry', 'IndexedDB', 'Session Storage', 'WebStorage')
|
|
17
|
+
|
|
18
|
+
$emailAddressPattern = '(?<![a-zA-Z0-9._%+-])[a-zA-Z0-9][a-zA-Z0-9._%+-]*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+(?![a-zA-Z0-9-])'
|
|
19
|
+
|
|
20
|
+
$maximumStorageFileBytes = 50MB
|
|
21
|
+
|
|
22
|
+
function Test-NoiseEmail {
|
|
23
|
+
param([string]$Email)
|
|
24
|
+
foreach ($pattern in $noiseEmailPatterns) {
|
|
25
|
+
if ($Email -imatch $pattern) { return $true }
|
|
26
|
+
}
|
|
27
|
+
return $false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Get-EncodingAgnosticTextView {
|
|
31
|
+
param([string]$FilePath)
|
|
32
|
+
try {
|
|
33
|
+
$fileBytes = [System.IO.File]::ReadAllBytes($FilePath)
|
|
34
|
+
if ($fileBytes.Length -eq 0) { return @() }
|
|
35
|
+
$isoLatin1Encoding = [System.Text.Encoding]::GetEncoding($isoLatin1CodePage)
|
|
36
|
+
return @(
|
|
37
|
+
$isoLatin1Encoding.GetString($fileBytes),
|
|
38
|
+
[System.Text.Encoding]::Unicode.GetString($fileBytes)
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return @()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Get-EmailCandidatesFromProfile {
|
|
47
|
+
param(
|
|
48
|
+
[string]$ProfileDirectory,
|
|
49
|
+
[string]$ExcludeEmail
|
|
50
|
+
)
|
|
51
|
+
$scanDirectories = $profileStorageDirectoryNames |
|
|
52
|
+
ForEach-Object { Join-Path $ProfileDirectory $_ } |
|
|
53
|
+
Where-Object { Test-Path -LiteralPath $_ }
|
|
54
|
+
$foundEmails = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
|
|
55
|
+
|
|
56
|
+
foreach ($scanDirectory in $scanDirectories) {
|
|
57
|
+
$candidateFiles = Get-ChildItem -LiteralPath $scanDirectory -Recurse -File -ErrorAction SilentlyContinue |
|
|
58
|
+
Where-Object { $_.Length -lt $maximumStorageFileBytes }
|
|
59
|
+
foreach ($candidateFile in $candidateFiles) {
|
|
60
|
+
$decodedTextViews = Get-EncodingAgnosticTextView -FilePath $candidateFile.FullName
|
|
61
|
+
foreach ($decodedText in $decodedTextViews) {
|
|
62
|
+
foreach ($match in [regex]::Matches($decodedText, $emailAddressPattern)) {
|
|
63
|
+
$candidateEmail = $match.Value
|
|
64
|
+
$isKnownCliEmail = $ExcludeEmail -and ($candidateEmail -ieq $ExcludeEmail)
|
|
65
|
+
if (-not (Test-NoiseEmail -Email $candidateEmail) -and -not $isKnownCliEmail) {
|
|
66
|
+
[void]$foundEmails.Add($candidateEmail)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return $foundEmails
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function Write-AccountResult {
|
|
76
|
+
param(
|
|
77
|
+
[string]$Email,
|
|
78
|
+
[string]$AccountUuid,
|
|
79
|
+
[string]$Source,
|
|
80
|
+
[string]$CliEmail,
|
|
81
|
+
[string]$CliAccountUuid,
|
|
82
|
+
[switch]$AsJson
|
|
83
|
+
)
|
|
84
|
+
if ($AsJson) {
|
|
85
|
+
[pscustomobject]@{
|
|
86
|
+
account = $Email
|
|
87
|
+
accountUuid = $AccountUuid
|
|
88
|
+
source = $Source
|
|
89
|
+
cliAccount = $CliEmail
|
|
90
|
+
cliAccountUuid = $CliAccountUuid
|
|
91
|
+
} | ConvertTo-Json
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
Write-Output "Account: $Email"
|
|
95
|
+
Write-Output "AccountUuid: $AccountUuid"
|
|
96
|
+
Write-Output "Source: $Source"
|
|
97
|
+
if ($CliEmail) {
|
|
98
|
+
Write-Output "CliAccount: $CliEmail"
|
|
99
|
+
Write-Output "CliAccountUuid: $CliAccountUuid"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function Get-SessionAccountResult {
|
|
104
|
+
param(
|
|
105
|
+
[int]$ExitCode,
|
|
106
|
+
[string]$Email = $null,
|
|
107
|
+
[string]$AccountUuid = $null,
|
|
108
|
+
[string]$Source = $null,
|
|
109
|
+
[string]$CliEmail = $null,
|
|
110
|
+
[string]$CliAccountUuid = $null,
|
|
111
|
+
[string]$ErrorMessage = $null
|
|
112
|
+
)
|
|
113
|
+
[pscustomobject]@{
|
|
114
|
+
ExitCode = $ExitCode
|
|
115
|
+
Email = $Email
|
|
116
|
+
AccountUuid = $AccountUuid
|
|
117
|
+
Source = $Source
|
|
118
|
+
CliEmail = $CliEmail
|
|
119
|
+
CliAccountUuid = $CliAccountUuid
|
|
120
|
+
ErrorMessage = $ErrorMessage
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function Resolve-SessionAccount {
|
|
125
|
+
param(
|
|
126
|
+
[string]$CliConfigPath,
|
|
127
|
+
[string]$ProfileDirectory
|
|
128
|
+
)
|
|
129
|
+
if (-not (Test-Path -LiteralPath $CliConfigPath)) {
|
|
130
|
+
return Get-SessionAccountResult -ExitCode 2 -ErrorMessage "CLI config not found: $CliConfigPath"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
$cliConfig = Get-Content -LiteralPath $CliConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json -AsHashtable -ErrorAction Stop
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return Get-SessionAccountResult -ExitCode 2 -ErrorMessage "Failed to parse CLI config as JSON: $CliConfigPath"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if ($cliConfig -isnot [System.Collections.IDictionary]) {
|
|
141
|
+
return Get-SessionAccountResult -ExitCode 2 -ErrorMessage "CLI config top level is not a JSON object: $CliConfigPath"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
$oauthAccount = $cliConfig['oauthAccount']
|
|
145
|
+
$cliEmail = if ($oauthAccount) { $oauthAccount['emailAddress'] } else { $null }
|
|
146
|
+
$cliAccountUuid = if ($oauthAccount) { $oauthAccount['accountUuid'] } else { $null }
|
|
147
|
+
if (-not $cliEmail -or -not $cliAccountUuid) {
|
|
148
|
+
return Get-SessionAccountResult -ExitCode 2 -ErrorMessage "CLI config is missing oauthAccount.emailAddress or oauthAccount.accountUuid: $CliConfigPath"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (-not $ProfileDirectory -or -not (Test-Path -LiteralPath $ProfileDirectory)) {
|
|
152
|
+
return Get-SessionAccountResult -ExitCode 0 -Email $cliEmail -AccountUuid $cliAccountUuid -Source 'cli-config'
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
$profileConfigPath = Join-Path $ProfileDirectory 'config.json'
|
|
156
|
+
if (-not (Test-Path -LiteralPath $profileConfigPath)) {
|
|
157
|
+
return Get-SessionAccountResult -ExitCode 2 -ErrorMessage "Desktop profile config not found: $profileConfigPath"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
$profileConfig = Get-Content -LiteralPath $profileConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json -AsHashtable -ErrorAction Stop
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return Get-SessionAccountResult -ExitCode 2 -ErrorMessage "Failed to parse desktop profile config as JSON: $profileConfigPath"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if ($profileConfig -isnot [System.Collections.IDictionary]) {
|
|
168
|
+
return Get-SessionAccountResult -ExitCode 2 -ErrorMessage "Desktop profile config top level is not a JSON object: $profileConfigPath"
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
$profileAccountUuid = $profileConfig['lastKnownAccountUuid']
|
|
172
|
+
if (-not $profileAccountUuid) {
|
|
173
|
+
return Get-SessionAccountResult -ExitCode 2 -ErrorMessage "Desktop profile config is missing lastKnownAccountUuid: $profileConfigPath"
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if ($profileAccountUuid -eq $cliAccountUuid) {
|
|
177
|
+
return Get-SessionAccountResult -ExitCode 0 -Email $cliEmail -AccountUuid $cliAccountUuid -Source 'desktop-profile (matches cli-config)'
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
$candidateEmails = @(Get-EmailCandidatesFromProfile -ProfileDirectory $ProfileDirectory -ExcludeEmail $cliEmail)
|
|
181
|
+
if ($candidateEmails.Count -eq 1) {
|
|
182
|
+
return Get-SessionAccountResult -ExitCode 0 -Email $candidateEmails[0] -AccountUuid $profileAccountUuid -Source 'desktop-profile (differs from cli-config)' -CliEmail $cliEmail -CliAccountUuid $cliAccountUuid
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if ($candidateEmails.Count -eq 0) {
|
|
186
|
+
return Get-SessionAccountResult -ExitCode 1 -ErrorMessage "Desktop profile account ($profileAccountUuid) differs from CLI config, but no candidate email was recovered from profile storage: $ProfileDirectory"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return Get-SessionAccountResult -ExitCode 1 -ErrorMessage "Desktop profile account ($profileAccountUuid) differs from CLI config, and multiple candidate emails were found in profile storage: $($candidateEmails -join ', ')"
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function Invoke-GetSessionAccount {
|
|
193
|
+
param(
|
|
194
|
+
[switch]$AsJson
|
|
195
|
+
)
|
|
196
|
+
$cliConfigPath = Join-Path $HOME '.claude.json'
|
|
197
|
+
$sessionAccount = Resolve-SessionAccount -CliConfigPath $cliConfigPath -ProfileDirectory $env:CLAUDE_USER_DATA_DIR
|
|
198
|
+
if ($sessionAccount.ErrorMessage) {
|
|
199
|
+
Write-Error $sessionAccount.ErrorMessage -ErrorAction Continue
|
|
200
|
+
exit $sessionAccount.ExitCode
|
|
201
|
+
}
|
|
202
|
+
Write-AccountResult -Email $sessionAccount.Email -AccountUuid $sessionAccount.AccountUuid -Source $sessionAccount.Source -CliEmail $sessionAccount.CliEmail -CliAccountUuid $sessionAccount.CliAccountUuid -AsJson:$AsJson
|
|
203
|
+
exit $sessionAccount.ExitCode
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if ($MyInvocation.InvocationName -ne '.') {
|
|
207
|
+
Set-StrictMode -Version Latest
|
|
208
|
+
$ErrorActionPreference = 'Stop'
|
|
209
|
+
Invoke-GetSessionAccount -AsJson:$AsJson
|
|
210
|
+
}
|
package/scripts/tests/CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# scripts/tests
|
|
2
2
|
|
|
3
|
-
pytest suite for the Python scripts in `scripts/`.
|
|
3
|
+
pytest suite for the Python scripts and Pester suite for the PowerShell scripts in `scripts/`.
|
|
4
4
|
|
|
5
5
|
## Test files
|
|
6
6
|
|
|
@@ -11,8 +11,22 @@ pytest suite for the Python scripts in `scripts/`.
|
|
|
11
11
|
| `test_sweep_empty_dirs.py` | `sweep_empty_dirs.py` — age check, one-shot mode, and continuous-watch behavior |
|
|
12
12
|
| `test_sync_to_cursor.py` | `sync_to_cursor/` package — mapping, hashing, manifest, and path resolution |
|
|
13
13
|
|
|
14
|
+
## PowerShell test files
|
|
15
|
+
|
|
16
|
+
| File | Covers |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `Get-SessionAccount.Tests.ps1` | `Get-SessionAccount.ps1` — noise filtering, profile-storage email recovery, CLI-vs-desktop account resolution, and output formatting |
|
|
19
|
+
|
|
14
20
|
## Running
|
|
15
21
|
|
|
22
|
+
Python scripts (pytest):
|
|
23
|
+
|
|
16
24
|
```bash
|
|
17
25
|
python -m pytest packages/claude-dev-env/scripts/tests/
|
|
18
26
|
```
|
|
27
|
+
|
|
28
|
+
PowerShell scripts (Pester 5+, `*.Tests.ps1`):
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
Invoke-Pester -Path packages/claude-dev-env/scripts/tests/
|
|
32
|
+
```
|