@rubytech/create-maxy-code 0.1.97 → 0.1.99
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/package.json +1 -1
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/founder-pack/.claude-plugin/plugin.json +8 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/SKILL.md +256 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/references/business-plan-template.md +61 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/references/compliance-research-checklist.md +53 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/references/data-room-structure.md +77 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/references/deck-blueprint-template.md +38 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/references/design-tokens-application.md +79 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/references/html-pdf-pipeline.md +236 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/references/internal-workings-scrub.md +33 -0
- package/payload/platform/plugins/founder-pack/skills/investor-data-room/references/termsheet-template.md +66 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +79 -87
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/scripts/stash-cleanup.sh +410 -0
- package/payload/premium-plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/premium-plugins/founder-pack/.claude-plugin/plugin.json +8 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/SKILL.md +256 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/references/business-plan-template.md +61 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/references/compliance-research-checklist.md +53 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/references/data-room-structure.md +77 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/references/deck-blueprint-template.md +38 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/references/design-tokens-application.md +79 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/references/html-pdf-pipeline.md +236 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/references/internal-workings-scrub.md +33 -0
- package/payload/premium-plugins/founder-pack/skills/investor-data-room/references/termsheet-template.md +66 -0
- package/payload/server/public/assets/{ChatInput-CzQhPt90.css → ChatInput-u7hSrC3q.css} +1 -1
- package/payload/server/public/assets/{Checkbox-ynZtDWi3.js → Checkbox-CfiWjuMH.js} +1 -1
- package/payload/server/public/assets/{admin-WyIfDMQG.js → admin-D6VRETeg.js} +1 -1
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Ko_izKmY.js → architectureDiagram-Q4EWVU46-CSAyzBIK.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-D58VDC0w.js → blockDiagram-DXYQGD6D-l_AoJo_I.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-C-eRXyKi.js → c4Diagram-AHTNJAMY-Dsjc59iO.js} +1 -1
- package/payload/server/public/assets/channel-BlwH4wTf.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O-DZLEfLAb.js → chunk-336JU56O-BgAz-EJ4.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-BriTU3L8.js → chunk-426QAEUC-DHF9zZlS.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-BdxRL-AY.js → chunk-4TB4RGXK-Cqb1TREv.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-BgEmy7Y3.js → chunk-5FUZZQ4R-CV4WMN8u.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-emnbDRLA.js → chunk-5PVQY5BW-CL0bAr0t.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-BvCf2Xf2.js → chunk-EDXVE4YY-WY3Sg1Fq.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-DTpEYJRb.js → chunk-ENJZ2VHE-BIVdDrD2.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-Ced2j48Y.js → chunk-ICPOFSXX-CUHDQxso.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-B21_a3Dv.js → chunk-OYMX7WX6-D1sj6R4f.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-CPS3yyXK.js → chunk-U2HBQHQK-BGso49fW.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-BJ_B5NW7.js → chunk-X2U36JSP-C2tUGTcL.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-CMLy-WXm.js → chunk-YZCP3GAM-CLvYLDvo.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-BkLnzCHe.js → chunk-ZZ45TVLE-yHQe8STh.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DuEhfP67.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-zSqemF--.js +1 -0
- package/payload/server/public/assets/clone-DUSqJbTD.js +1 -0
- package/payload/server/public/assets/{dagre-KV5264BT-RY0E5UAn.js → dagre-KV5264BT-Df1MO_DJ.js} +1 -1
- package/payload/server/public/assets/{dagre-Dd_gq4yj.js → dagre-fQyUaxmN.js} +1 -1
- package/payload/server/public/assets/data-BFHNkj3Y.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-DR7XiPLh.js → diagram-5BDNPKRD-E3qTf9lX.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-2EbOfKFs.js → diagram-G4DWMVQ6-DCV-gR0I.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-CGcY0Ze7.js → diagram-MMDJMWI5-CZKwrxSp.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-Dk6MmAso.js → diagram-TYMM5635-ClXOl5De.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-CKvdlUqk.js → erDiagram-SMLLAGMA-v2-MNn-e.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BqKhvCDz.js → flowDiagram-DWJPFMVM-CDFzDH-2.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CeiE-IRb.js → ganttDiagram-T4ZO3ILL-DEA-33qn.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-BbPKn7v2.js → gitGraphDiagram-UUTBAWPF-lLy_eZzu.js} +1 -1
- package/payload/server/public/assets/graph-Df2aJN0a.js +1 -0
- package/payload/server/public/assets/{graph-labels-BzMN9eev.js → graph-labels-CCTdQ2Vi.js} +1 -1
- package/payload/server/public/assets/{graphlib-tG6kR59B.js → graphlib-B6Y7yD4A.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-6MiRGCI1.js → infoDiagram-42DDH7IO-D-Pm-tYB.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BwY4p5mg.js → ishikawaDiagram-UXIWVN3A-DVHkDPv1.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-BXAQ_fUi.js → journeyDiagram-VCZTEJTY-CsxM47ah.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-ChiiO7Yt.js → kanban-definition-6JOO6SKY-C7EYwNjH.js} +1 -1
- package/payload/server/public/assets/{lib-DbPz9cfu.js → lib-iH6Mg9EP.js} +1 -1
- package/payload/server/public/assets/{line-B5g5OrOD.js → line-CvBly01q.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-BMGzMKJx.js → mermaid-parser.core-D32akjeh.js} +1 -1
- package/payload/server/public/assets/{mermaid.core-DLMgkjXn.js → mermaid.core-B3O4X8ja.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-BvyzRLAi.js → mindmap-definition-QFDTVHPH-yreoqnSK.js} +1 -1
- package/payload/server/public/assets/{page-D93GZ3YQ.js → page-BVuuY21i.js} +1 -1
- package/payload/server/public/assets/{page--nZ_z-z7.js → page-Dc2CtJkh.js} +1 -1
- package/payload/server/public/assets/{pieDiagram-DEJITSTG--EV9pVfs.js → pieDiagram-DEJITSTG-DEYXlFD5.js} +1 -1
- package/payload/server/public/assets/{public-sYW0Dwku.js → public-DknvBIqv.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-DJcL08ft.js → quadrantDiagram-34T5L4WZ-CVTVYl5c.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-D4VdmmSu.js → requirementDiagram-MS252O5E-D438ZRqW.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-Dsw3nHRS.js → sankeyDiagram-XADWPNL6-B2Hs1q9K.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-CAgemUx5.js → sequenceDiagram-FGHM5R23-BqRHtAoK.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-B7dyL9_X.js → stateDiagram-FHFEXIEX-ILOj6_TV.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-B2cQBKI1.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-B7iKswhp.js → timeline-definition-GMOUNBTQ-D-uskEEs.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-uFS0i1qX.js → vennDiagram-DHZGUBPP-DJjaoR79.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-lDUh4LAD.js → wardleyDiagram-NUSXRM2D-B1EO_5jp.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-0v7MEVwW.js → xychartDiagram-5P7HB3ND-CQnz0KUb.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +8 -8
- package/payload/server/public/public.html +5 -5
- package/payload/server/public/assets/channel-CejaITMF.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-BmoqALf4.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-vMajwCNi.js +0 -1
- package/payload/server/public/assets/clone-mvVfHkyg.js +0 -1
- package/payload/server/public/assets/data-D2aPJB7w.js +0 -1
- package/payload/server/public/assets/graph-B5UWg8zV.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CImN6Wk6.js +0 -1
- /package/payload/server/public/assets/{ChatInput-Cj9JQtYt.js → ChatInput-BmmxQOhM.js} +0 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Task 273 — one-time JSONL stash-composite cleanup.
|
|
3
|
+
#
|
|
4
|
+
# For each `<uuid>.<unix-ms>.jsonl` file under a CLAUDE_STATE_ROOT/projects
|
|
5
|
+
# directory (top-level and archive/ subdir), pair it with the canonical
|
|
6
|
+
# `<uuid>.jsonl`. Keep the larger of the two as the canonical; move the
|
|
7
|
+
# smaller (with its sidecar) to QUARANTINE_ROOT, preserving the original
|
|
8
|
+
# basename. If only the timestamped sibling exists (no canonical), promote
|
|
9
|
+
# it to the canonical name. The quarantine path sits outside any directory
|
|
10
|
+
# the fs-watcher scans, so quarantined files do not appear in the sidebar.
|
|
11
|
+
#
|
|
12
|
+
# Safety rails:
|
|
13
|
+
# 1. Refuse to run if any PID file exists under CLAUDE_STATE_ROOT/sessions/.
|
|
14
|
+
# A live PTY holding one of the affected UUIDs is the failure mode this
|
|
15
|
+
# check guards against.
|
|
16
|
+
# 2. Refuse to overwrite an existing quarantine target — if the same
|
|
17
|
+
# timestamped basename already sits in quarantine from a prior run,
|
|
18
|
+
# surface the conflict and skip rather than clobber.
|
|
19
|
+
# 3. Sidecar `.meta.json` files follow the same disposition as their JSONL.
|
|
20
|
+
# 4. A `.log` audit trail records every rename and quarantine move with
|
|
21
|
+
# from=, to=, bytes=, mtime=. The transcript is the operator's
|
|
22
|
+
# audit before approving permanent deletion in a follow-up task.
|
|
23
|
+
#
|
|
24
|
+
# Usage:
|
|
25
|
+
# stash-cleanup.sh <claude-state-root> [--dry-run]
|
|
26
|
+
# e.g. ~/.realagent-code/.claude
|
|
27
|
+
#
|
|
28
|
+
# stash-cleanup.sh --self-test
|
|
29
|
+
# Seeds a temp dir with collision pairs, runs the cleanup against it,
|
|
30
|
+
# asserts post-conditions, and cleans up. Exits non-zero on failure.
|
|
31
|
+
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
|
|
34
|
+
usage() {
|
|
35
|
+
cat <<USAGE
|
|
36
|
+
Task 273 JSONL stash-cleanup.
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
$0 <claude-state-root> [--dry-run]
|
|
40
|
+
$0 --self-test
|
|
41
|
+
|
|
42
|
+
Arguments:
|
|
43
|
+
<claude-state-root> e.g. ~/.realagent-code/.claude
|
|
44
|
+
|
|
45
|
+
Flags:
|
|
46
|
+
--dry-run Plan-only: log what would happen, change nothing on disk.
|
|
47
|
+
--self-test Run the embedded smoke test; exits 0 on PASS.
|
|
48
|
+
USAGE
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
log() { printf '[stash-cleanup] %s\n' "$*"; }
|
|
52
|
+
err() { printf '[stash-cleanup] ERROR: %s\n' "$*" >&2; }
|
|
53
|
+
|
|
54
|
+
# Returns 0 if the basename matches `<uuid>.<unix-ms>.jsonl`. UUID is
|
|
55
|
+
# 36 chars including dashes; ms timestamp is exactly 13 digits.
|
|
56
|
+
is_stash_composite_basename() {
|
|
57
|
+
local name="$1"
|
|
58
|
+
[[ "$name" =~ ^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\.[0-9]{13}\.jsonl$ ]]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Extract the canonical `<uuid>.jsonl` basename from a stash composite.
|
|
62
|
+
canonical_basename_for() {
|
|
63
|
+
local stash="$1"
|
|
64
|
+
printf '%s.jsonl\n' "${stash:0:36}"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
bytes_of() {
|
|
68
|
+
if [[ -f "$1" ]]; then
|
|
69
|
+
stat -f%z "$1" 2>/dev/null || stat -c%s "$1"
|
|
70
|
+
else
|
|
71
|
+
printf '0\n'
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
mtime_of() {
|
|
76
|
+
if [[ -f "$1" ]]; then
|
|
77
|
+
stat -f%m "$1" 2>/dev/null || stat -c%Y "$1"
|
|
78
|
+
else
|
|
79
|
+
printf '0\n'
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Move a JSONL file (and its sidecar if present) to quarantine, preserving
|
|
84
|
+
# the original basename. Refuses to clobber.
|
|
85
|
+
move_to_quarantine() {
|
|
86
|
+
local src="$1" qdir="$2" reason="$3"
|
|
87
|
+
local bn dst sidecar_src sidecar_dst
|
|
88
|
+
bn=$(basename "$src")
|
|
89
|
+
dst="$qdir/$bn"
|
|
90
|
+
|
|
91
|
+
if [[ -e "$dst" ]]; then
|
|
92
|
+
err "quarantine target already exists, skipping: $dst"
|
|
93
|
+
return 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
local sz mt
|
|
97
|
+
sz=$(bytes_of "$src")
|
|
98
|
+
mt=$(mtime_of "$src")
|
|
99
|
+
if [[ "$DRY_RUN" == "yes" ]]; then
|
|
100
|
+
log "DRY quarantine reason=$reason from=$src to=$dst bytes=$sz mtime=$mt"
|
|
101
|
+
else
|
|
102
|
+
mkdir -p "$qdir"
|
|
103
|
+
mv "$src" "$dst"
|
|
104
|
+
log "quarantine reason=$reason from=$src to=$dst bytes=$sz mtime=$mt"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Sidecar follows: `<base>.meta.json` where <base> is the JSONL path minus
|
|
108
|
+
# the trailing `.jsonl`.
|
|
109
|
+
sidecar_src="${src%.jsonl}.meta.json"
|
|
110
|
+
sidecar_dst="${dst%.jsonl}.meta.json"
|
|
111
|
+
if [[ -f "$sidecar_src" ]]; then
|
|
112
|
+
if [[ -e "$sidecar_dst" ]]; then
|
|
113
|
+
err "sidecar quarantine target already exists, skipping: $sidecar_dst"
|
|
114
|
+
return 1
|
|
115
|
+
fi
|
|
116
|
+
local ssz smt
|
|
117
|
+
ssz=$(bytes_of "$sidecar_src")
|
|
118
|
+
smt=$(mtime_of "$sidecar_src")
|
|
119
|
+
if [[ "$DRY_RUN" == "yes" ]]; then
|
|
120
|
+
log "DRY quarantine-sidecar from=$sidecar_src to=$sidecar_dst bytes=$ssz mtime=$smt"
|
|
121
|
+
else
|
|
122
|
+
mv "$sidecar_src" "$sidecar_dst"
|
|
123
|
+
log "quarantine-sidecar from=$sidecar_src to=$sidecar_dst bytes=$ssz mtime=$smt"
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
# Promote `src` to `dst` by renaming. Sidecar follows.
|
|
129
|
+
promote_in_place() {
|
|
130
|
+
local src="$1" dst="$2" reason="$3"
|
|
131
|
+
local sidecar_src sidecar_dst
|
|
132
|
+
local sz mt
|
|
133
|
+
sz=$(bytes_of "$src")
|
|
134
|
+
mt=$(mtime_of "$src")
|
|
135
|
+
if [[ "$DRY_RUN" == "yes" ]]; then
|
|
136
|
+
log "DRY promote reason=$reason from=$src to=$dst bytes=$sz mtime=$mt"
|
|
137
|
+
else
|
|
138
|
+
mv "$src" "$dst"
|
|
139
|
+
log "promote reason=$reason from=$src to=$dst bytes=$sz mtime=$mt"
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
sidecar_src="${src%.jsonl}.meta.json"
|
|
143
|
+
sidecar_dst="${dst%.jsonl}.meta.json"
|
|
144
|
+
if [[ -f "$sidecar_src" ]]; then
|
|
145
|
+
local ssz smt
|
|
146
|
+
ssz=$(bytes_of "$sidecar_src")
|
|
147
|
+
smt=$(mtime_of "$sidecar_src")
|
|
148
|
+
if [[ "$DRY_RUN" == "yes" ]]; then
|
|
149
|
+
log "DRY promote-sidecar from=$sidecar_src to=$sidecar_dst bytes=$ssz mtime=$smt"
|
|
150
|
+
else
|
|
151
|
+
mv "$sidecar_src" "$sidecar_dst"
|
|
152
|
+
log "promote-sidecar from=$sidecar_src to=$sidecar_dst bytes=$ssz mtime=$smt"
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# Process one directory (top-level or archive/). For each stash composite,
|
|
158
|
+
# pair with canonical; resolve.
|
|
159
|
+
process_dir() {
|
|
160
|
+
local dir="$1" qdir="$2"
|
|
161
|
+
[[ -d "$dir" ]] || { log "skip: $dir does not exist"; return 0; }
|
|
162
|
+
|
|
163
|
+
local handled=0
|
|
164
|
+
shopt -s nullglob
|
|
165
|
+
for path in "$dir"/*.jsonl; do
|
|
166
|
+
local bn
|
|
167
|
+
bn=$(basename "$path")
|
|
168
|
+
is_stash_composite_basename "$bn" || continue
|
|
169
|
+
handled=$((handled + 1))
|
|
170
|
+
|
|
171
|
+
local canonical_bn canonical_path
|
|
172
|
+
canonical_bn=$(canonical_basename_for "$bn")
|
|
173
|
+
canonical_path="$dir/$canonical_bn"
|
|
174
|
+
|
|
175
|
+
if [[ -f "$canonical_path" ]]; then
|
|
176
|
+
local stash_sz canonical_sz
|
|
177
|
+
stash_sz=$(bytes_of "$path")
|
|
178
|
+
canonical_sz=$(bytes_of "$canonical_path")
|
|
179
|
+
log "pair dir=$dir uuid=${bn:0:8} stash=$bn stash-bytes=$stash_sz canonical-bytes=$canonical_sz"
|
|
180
|
+
|
|
181
|
+
if (( stash_sz > canonical_sz )); then
|
|
182
|
+
# Stash wins. Quarantine canonical first (preserving its name),
|
|
183
|
+
# then promote stash to canonical.
|
|
184
|
+
local canonical_quarantine_name="$canonical_bn.canonical"
|
|
185
|
+
local canonical_qdst="$qdir/$canonical_quarantine_name"
|
|
186
|
+
if [[ -e "$canonical_qdst" ]]; then
|
|
187
|
+
err "canonical quarantine target exists, skipping pair: $canonical_qdst"
|
|
188
|
+
continue
|
|
189
|
+
fi
|
|
190
|
+
local csz cmt
|
|
191
|
+
csz=$(bytes_of "$canonical_path")
|
|
192
|
+
cmt=$(mtime_of "$canonical_path")
|
|
193
|
+
if [[ "$DRY_RUN" == "yes" ]]; then
|
|
194
|
+
log "DRY quarantine reason=smaller-canonical from=$canonical_path to=$canonical_qdst bytes=$csz mtime=$cmt"
|
|
195
|
+
else
|
|
196
|
+
mkdir -p "$qdir"
|
|
197
|
+
mv "$canonical_path" "$canonical_qdst"
|
|
198
|
+
log "quarantine reason=smaller-canonical from=$canonical_path to=$canonical_qdst bytes=$csz mtime=$cmt"
|
|
199
|
+
fi
|
|
200
|
+
# Canonical sidecar follows with the same suffix.
|
|
201
|
+
local csc_src="${canonical_path%.jsonl}.meta.json"
|
|
202
|
+
local csc_dst="${canonical_qdst%.jsonl}.meta.json"
|
|
203
|
+
if [[ -f "$csc_src" ]]; then
|
|
204
|
+
if [[ "$DRY_RUN" == "yes" ]]; then
|
|
205
|
+
log "DRY quarantine-sidecar from=$csc_src to=$csc_dst"
|
|
206
|
+
else
|
|
207
|
+
mv "$csc_src" "$csc_dst"
|
|
208
|
+
log "quarantine-sidecar from=$csc_src to=$csc_dst"
|
|
209
|
+
fi
|
|
210
|
+
fi
|
|
211
|
+
promote_in_place "$path" "$canonical_path" "stash-larger-than-canonical"
|
|
212
|
+
else
|
|
213
|
+
# Canonical wins (or equal). Quarantine stash with its original
|
|
214
|
+
# timestamped basename.
|
|
215
|
+
move_to_quarantine "$path" "$qdir" "smaller-stash" || true
|
|
216
|
+
fi
|
|
217
|
+
else
|
|
218
|
+
# No canonical on disk. Promote the stash to canonical.
|
|
219
|
+
log "lone-stash dir=$dir uuid=${bn:0:8} stash=$bn"
|
|
220
|
+
promote_in_place "$path" "$canonical_path" "no-canonical-present"
|
|
221
|
+
fi
|
|
222
|
+
done
|
|
223
|
+
shopt -u nullglob
|
|
224
|
+
|
|
225
|
+
log "summary dir=$dir composites-handled=$handled"
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Walks every project directory under <state-root>/projects/ and processes
|
|
229
|
+
# top-level + archive/ for each.
|
|
230
|
+
run_cleanup() {
|
|
231
|
+
local state_root="$1"
|
|
232
|
+
local projects_root="$state_root/projects"
|
|
233
|
+
local sessions_root="$state_root/sessions"
|
|
234
|
+
local quarantine_root="$state_root/quarantine"
|
|
235
|
+
|
|
236
|
+
[[ -d "$projects_root" ]] || { err "projects root not found: $projects_root"; return 2; }
|
|
237
|
+
|
|
238
|
+
# Pre-flight: no live PTY may be running. The sessions/ directory holds
|
|
239
|
+
# one PID file per live PTY (Task 199 contract); empty = no live PTYs.
|
|
240
|
+
if [[ -d "$sessions_root" ]]; then
|
|
241
|
+
shopt -s nullglob
|
|
242
|
+
local pids=("$sessions_root"/*.json)
|
|
243
|
+
shopt -u nullglob
|
|
244
|
+
if (( ${#pids[@]} > 0 )); then
|
|
245
|
+
err "live PTY detected — ${#pids[@]} PID file(s) under $sessions_root; refuse to touch on-disk state"
|
|
246
|
+
err "stop the affected sessions first, then retry"
|
|
247
|
+
return 3
|
|
248
|
+
fi
|
|
249
|
+
fi
|
|
250
|
+
log "pre-flight pid-files=0 sessions-root=$sessions_root"
|
|
251
|
+
|
|
252
|
+
shopt -s nullglob
|
|
253
|
+
local project_dirs=("$projects_root"/*/)
|
|
254
|
+
shopt -u nullglob
|
|
255
|
+
log "projects-found count=${#project_dirs[@]}"
|
|
256
|
+
|
|
257
|
+
for project_dir in "${project_dirs[@]}"; do
|
|
258
|
+
local slug
|
|
259
|
+
slug=$(basename "$project_dir")
|
|
260
|
+
local qdir="$quarantine_root/$slug"
|
|
261
|
+
log "project-begin slug=$slug"
|
|
262
|
+
process_dir "${project_dir%/}" "$qdir"
|
|
263
|
+
process_dir "${project_dir%/}/archive" "$qdir/archive"
|
|
264
|
+
log "project-end slug=$slug"
|
|
265
|
+
done
|
|
266
|
+
|
|
267
|
+
log "done state-root=$state_root quarantine-root=$quarantine_root dry-run=$DRY_RUN"
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# ----- self-test --------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
self_test() {
|
|
273
|
+
local tmp
|
|
274
|
+
tmp=$(mktemp -d)
|
|
275
|
+
# Expand at trap-set time so the path survives function-local scope.
|
|
276
|
+
trap "rm -rf '$tmp'" EXIT
|
|
277
|
+
|
|
278
|
+
local state="$tmp/state"
|
|
279
|
+
mkdir -p "$state/projects/slug-A" "$state/projects/slug-A/archive" "$state/sessions"
|
|
280
|
+
|
|
281
|
+
# Pair 1: timestamped sibling is larger; should win.
|
|
282
|
+
local uuid1="4bfe4274-1111-4111-8111-111111111111"
|
|
283
|
+
local big="payload-big"
|
|
284
|
+
local small="x"
|
|
285
|
+
printf '%s' "$big" > "$state/projects/slug-A/${uuid1}.1779477697566.jsonl"
|
|
286
|
+
printf '%s' "$small" > "$state/projects/slug-A/${uuid1}.jsonl"
|
|
287
|
+
|
|
288
|
+
# Pair 2: canonical is larger; sibling loses.
|
|
289
|
+
local uuid2="9c26cc66-2222-4222-8222-222222222222"
|
|
290
|
+
printf '%s' "big-canonical" > "$state/projects/slug-A/${uuid2}.jsonl"
|
|
291
|
+
printf '%s' "tiny" > "$state/projects/slug-A/${uuid2}.1779477700000.jsonl"
|
|
292
|
+
|
|
293
|
+
# Pair 3 (archive): canonical wins (matches the be23f93a archive-pair shape).
|
|
294
|
+
local uuid3="654f5fe5-3333-4333-8333-333333333333"
|
|
295
|
+
printf '%s' "archived-big-history" > "$state/projects/slug-A/archive/${uuid3}.jsonl"
|
|
296
|
+
printf '%s' "tiny" > "$state/projects/slug-A/archive/${uuid3}.1779477705000.jsonl"
|
|
297
|
+
|
|
298
|
+
# Pair 4: lone stash, no canonical present.
|
|
299
|
+
local uuid4="abcdef01-4444-4444-8444-444444444444"
|
|
300
|
+
printf '%s' "lone-stash-content" > "$state/projects/slug-A/${uuid4}.1779477710000.jsonl"
|
|
301
|
+
|
|
302
|
+
# Sidecar on uuid1's stash (the winner) — should travel with the rename
|
|
303
|
+
# to canonical. Sidecar on uuid2's stash (the loser) — should follow to
|
|
304
|
+
# quarantine.
|
|
305
|
+
printf '{"meta":"win"}' > "$state/projects/slug-A/${uuid1}.1779477697566.meta.json"
|
|
306
|
+
printf '{"meta":"lose"}' > "$state/projects/slug-A/${uuid2}.1779477700000.meta.json"
|
|
307
|
+
|
|
308
|
+
DRY_RUN=no run_cleanup "$state" >/tmp/stash-cleanup-selftest.log 2>&1 \
|
|
309
|
+
|| { cat /tmp/stash-cleanup-selftest.log; err "self-test: run_cleanup failed"; exit 1; }
|
|
310
|
+
|
|
311
|
+
local fail=0
|
|
312
|
+
local q="$state/quarantine/slug-A"
|
|
313
|
+
local qa="$state/quarantine/slug-A/archive"
|
|
314
|
+
|
|
315
|
+
# uuid1: canonical now holds "payload-big"; old canonical lives in
|
|
316
|
+
# quarantine with the `.canonical` suffix.
|
|
317
|
+
if [[ "$(cat "$state/projects/slug-A/${uuid1}.jsonl" 2>/dev/null)" != "$big" ]]; then
|
|
318
|
+
err "uuid1 canonical did not receive stash content"; fail=1
|
|
319
|
+
fi
|
|
320
|
+
if [[ -e "$state/projects/slug-A/${uuid1}.1779477697566.jsonl" ]]; then
|
|
321
|
+
err "uuid1 stash sibling still on disk after promotion"; fail=1
|
|
322
|
+
fi
|
|
323
|
+
if [[ "$(cat "$q/${uuid1}.jsonl.canonical" 2>/dev/null)" != "$small" ]]; then
|
|
324
|
+
err "uuid1 old canonical not in quarantine"; fail=1
|
|
325
|
+
fi
|
|
326
|
+
# Sidecar followed promotion.
|
|
327
|
+
if [[ "$(cat "$state/projects/slug-A/${uuid1}.meta.json" 2>/dev/null)" != '{"meta":"win"}' ]]; then
|
|
328
|
+
err "uuid1 sidecar did not follow promotion"; fail=1
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
# uuid2: canonical untouched; stash sibling in quarantine.
|
|
332
|
+
if [[ "$(cat "$state/projects/slug-A/${uuid2}.jsonl" 2>/dev/null)" != "big-canonical" ]]; then
|
|
333
|
+
err "uuid2 canonical altered"; fail=1
|
|
334
|
+
fi
|
|
335
|
+
if [[ ! -f "$q/${uuid2}.1779477700000.jsonl" ]]; then
|
|
336
|
+
err "uuid2 stash sibling not quarantined"; fail=1
|
|
337
|
+
fi
|
|
338
|
+
if [[ "$(cat "$q/${uuid2}.1779477700000.meta.json" 2>/dev/null)" != '{"meta":"lose"}' ]]; then
|
|
339
|
+
err "uuid2 sidecar did not follow to quarantine"; fail=1
|
|
340
|
+
fi
|
|
341
|
+
|
|
342
|
+
# uuid3 (archive): canonical wins; stash sibling in archive-quarantine.
|
|
343
|
+
if [[ "$(cat "$state/projects/slug-A/archive/${uuid3}.jsonl" 2>/dev/null)" != "archived-big-history" ]]; then
|
|
344
|
+
err "uuid3 archive canonical altered"; fail=1
|
|
345
|
+
fi
|
|
346
|
+
if [[ ! -f "$qa/${uuid3}.1779477705000.jsonl" ]]; then
|
|
347
|
+
err "uuid3 archive stash sibling not quarantined under archive/"; fail=1
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
# uuid4: lone stash promoted in place; no quarantine.
|
|
351
|
+
if [[ "$(cat "$state/projects/slug-A/${uuid4}.jsonl" 2>/dev/null)" != "lone-stash-content" ]]; then
|
|
352
|
+
err "uuid4 lone stash not promoted to canonical"; fail=1
|
|
353
|
+
fi
|
|
354
|
+
if [[ -e "$state/projects/slug-A/${uuid4}.1779477710000.jsonl" ]]; then
|
|
355
|
+
err "uuid4 stash file still on disk after promotion"; fail=1
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
# No <uuid>.<unix-ms>.jsonl siblings remain in the projects scan scope.
|
|
359
|
+
local residual
|
|
360
|
+
residual=$(find "$state/projects" -type f -name '*.jsonl' \
|
|
361
|
+
| awk -F/ '{ print $NF }' \
|
|
362
|
+
| grep -cE '^[a-f0-9-]{36}\.[0-9]{13}\.jsonl$' || true)
|
|
363
|
+
if (( residual > 0 )); then
|
|
364
|
+
err "self-test: $residual timestamped siblings still present"; fail=1
|
|
365
|
+
fi
|
|
366
|
+
|
|
367
|
+
# Live-PTY guard: seed a PID file and confirm the script refuses.
|
|
368
|
+
printf '{"pid":1234}' > "$state/sessions/1234.json"
|
|
369
|
+
if DRY_RUN=no run_cleanup "$state" >>/tmp/stash-cleanup-selftest.log 2>&1; then
|
|
370
|
+
err "self-test: live-PTY guard did not refuse"; fail=1
|
|
371
|
+
fi
|
|
372
|
+
rm -f "$state/sessions/1234.json"
|
|
373
|
+
|
|
374
|
+
if (( fail > 0 )); then
|
|
375
|
+
cat /tmp/stash-cleanup-selftest.log
|
|
376
|
+
err "self-test: FAILED"
|
|
377
|
+
exit 1
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
log "self-test: PASS"
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
# ----- entry point ------------------------------------------------------
|
|
384
|
+
|
|
385
|
+
DRY_RUN=no
|
|
386
|
+
case "${1:-}" in
|
|
387
|
+
-h|--help|"")
|
|
388
|
+
usage
|
|
389
|
+
exit 0
|
|
390
|
+
;;
|
|
391
|
+
--self-test)
|
|
392
|
+
self_test
|
|
393
|
+
exit 0
|
|
394
|
+
;;
|
|
395
|
+
*)
|
|
396
|
+
STATE_ROOT="${1:-}"
|
|
397
|
+
shift || true
|
|
398
|
+
while (( $# > 0 )); do
|
|
399
|
+
case "$1" in
|
|
400
|
+
--dry-run) DRY_RUN=yes ;;
|
|
401
|
+
*) err "unknown flag: $1"; usage; exit 2 ;;
|
|
402
|
+
esac
|
|
403
|
+
shift
|
|
404
|
+
done
|
|
405
|
+
[[ -n "$STATE_ROOT" ]] || { usage; exit 2; }
|
|
406
|
+
# Expand ~ since the caller may pass it.
|
|
407
|
+
STATE_ROOT="${STATE_ROOT/#\~/$HOME}"
|
|
408
|
+
run_cleanup "$STATE_ROOT" || exit $?
|
|
409
|
+
;;
|
|
410
|
+
esac
|