@planu/cli 5.3.13 → 5.3.15
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/CHANGELOG.md +30 -0
- package/dist/engine/cascade-hooks/hooks/housekeeping-on-done.hook.js +2 -3
- package/dist/engine/handoff-packager.d.ts +2 -2
- package/dist/engine/handoff-packager.js +5 -4
- package/dist/engine/housekeeping/find-stale-branches.d.ts +3 -3
- package/dist/engine/housekeeping/find-stale-branches.js +25 -29
- package/dist/engine/housekeeping/sweep-runner.js +105 -19
- package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-arm64.node.sbom.json +8 -8
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +8 -8
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +8 -8
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +8 -8
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +8 -8
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +8 -8
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +8 -8
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +8 -8
- package/dist/engine/spec-migrator/planu-root-cleaner.d.ts +2 -2
- package/dist/engine/spec-migrator/planu-root-cleaner.js +3 -2
- package/dist/engine/spec-migrator/strict-planu-cleanup.d.ts +2 -2
- package/dist/engine/spec-migrator/strict-planu-cleanup.js +18 -2
- package/dist/tools/git/cleanup-ops.d.ts +2 -2
- package/dist/tools/git/cleanup-ops.js +123 -58
- package/dist/tools/github-release-handler.js +3 -12
- package/dist/tools/housekeeping-sweep.d.ts +1 -0
- package/dist/tools/housekeeping-sweep.js +17 -10
- package/dist/tools/init-project/git-setup.d.ts +1 -0
- package/dist/tools/init-project/git-setup.js +7 -23
- package/dist/tools/init-project/handler.js +4 -1
- package/dist/tools/init-project/migration-runner.js +8 -1
- package/dist/tools/init-project/scaffold-writer.d.ts +1 -0
- package/dist/tools/init-project/scaffold-writer.js +1 -0
- package/dist/tools/update-status/batch.js +1 -1
- package/dist/tools/update-status/index.js +1 -0
- package/dist/tools/update-status-actions.d.ts +2 -2
- package/dist/tools/update-status-actions.js +20 -10
- package/dist/types/git.d.ts +19 -0
- package/dist/types/housekeeping.d.ts +9 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/spec-format.d.ts +3 -0
- package/package.json +11 -10
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## [5.3.15] - 2026-08-11
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix(spec-1403): apply implementation-review arbitration fixes
|
|
5
|
+
- fix(spec-1403): make destructive housekeeping report-only under typed authority
|
|
6
|
+
|
|
7
|
+
### Chores
|
|
8
|
+
- chore(spec-1403): record done transition with arbitration evidence
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## [5.3.14] - 2026-08-10
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
- fix(spec-1481): bound trapped fixture life per implementation-review arbitration
|
|
15
|
+
- fix(spec-1481): harden stdio abort test vs load and de-vacuize pid asserts
|
|
16
|
+
- fix(spec-1466): apply implementation-review arbitration fixes
|
|
17
|
+
- fix(spec-1467): require positive integer pid in waitForPid per review arbitration
|
|
18
|
+
- fix(spec-1466): legalize test-evidence overlap in ownership gate and handoff agreement
|
|
19
|
+
- fix(spec-1467): harden timing-sensitive receipt and process-runner tests
|
|
20
|
+
|
|
21
|
+
### Chores
|
|
22
|
+
- chore(specs): file SPEC-1484 durable heartbeat flaky test dogfood bug
|
|
23
|
+
- chore(specs): file SPEC-1483 stale native artifacts after release abort
|
|
24
|
+
- chore(specs): record SPEC-1481 done transition and file SPEC-1482 lint-gate bug
|
|
25
|
+
- chore(specs): file SPEC-1481 flaky stdio lifecycle test under full-suite load
|
|
26
|
+
- chore(specs): record done transitions for SPEC-1466/1467 and file SPEC-1480 debt spec
|
|
27
|
+
- chore(specs): record implementing transitions for SPEC-1466/1467 with handoff evidence
|
|
28
|
+
- chore(specs): approve SPEC-1466/1467 after round 2; file SPEC-1479
|
|
29
|
+
|
|
30
|
+
|
|
1
31
|
## [5.3.13] - 2026-08-10
|
|
2
32
|
|
|
3
33
|
### Bug Fixes
|
|
@@ -9,8 +9,7 @@ async function handler(ctx) {
|
|
|
9
9
|
try {
|
|
10
10
|
// Dynamic import keeps module graph lean — housekeeping-sweep is only needed on 'done'.
|
|
11
11
|
const { runHousekeepingSweep } = await import('../../housekeeping/index.js');
|
|
12
|
-
|
|
13
|
-
void runHousekeepingSweep({ projectPath, dryRun: false });
|
|
12
|
+
void runHousekeepingSweep({ projectPath });
|
|
14
13
|
}
|
|
15
14
|
catch (err) {
|
|
16
15
|
const error = err instanceof Error ? err.message : String(err);
|
|
@@ -29,7 +28,7 @@ export const housekeepingOnDoneHook = {
|
|
|
29
28
|
id: 'housekeeping-on-done',
|
|
30
29
|
triggers: ['done'],
|
|
31
30
|
requiresProjectPath: true,
|
|
32
|
-
label: 'Auto-run housekeeping_sweep on spec done
|
|
31
|
+
label: 'Auto-run report-only housekeeping_sweep on spec done (SPEC-782)',
|
|
33
32
|
handler,
|
|
34
33
|
};
|
|
35
34
|
//# sourceMappingURL=housekeeping-on-done.hook.js.map
|
|
@@ -16,8 +16,8 @@ export declare function extractCanonicalFileOwnership(content: string): {
|
|
|
16
16
|
toModify: string[];
|
|
17
17
|
toCreate: string[];
|
|
18
18
|
toTest: string[];
|
|
19
|
-
/** Test paths as declared, before conflict
|
|
20
|
-
*
|
|
19
|
+
/** Test paths as declared, before conflict stripping — feeds the package-level
|
|
20
|
+
* test filter and the ownership-agreement exclusion. */
|
|
21
21
|
toTestDeclared: string[];
|
|
22
22
|
blockers: string[];
|
|
23
23
|
};
|
|
@@ -213,7 +213,7 @@ export function extractCanonicalFileOwnership(content) {
|
|
|
213
213
|
const toCreate = parseSection('Create');
|
|
214
214
|
const toModify = parseSection('Modify');
|
|
215
215
|
const toTest = parseSection('Test');
|
|
216
|
-
const conflicts =
|
|
216
|
+
const conflicts = toCreate.filter((path) => toModify.includes(path));
|
|
217
217
|
for (const path of conflicts) {
|
|
218
218
|
addBlocker(`CONFLICTING_FILE_OWNERSHIP: ${path}`);
|
|
219
219
|
}
|
|
@@ -411,10 +411,11 @@ function extractFileLevelWorkPlanOwnership(content) {
|
|
|
411
411
|
blockers: [...new Set(state.blockers)].sort(),
|
|
412
412
|
};
|
|
413
413
|
}
|
|
414
|
-
function validateOwnershipAgreement(canonical, workPlan) {
|
|
414
|
+
function validateOwnershipAgreement(canonical, workPlan, testDeclaredPaths) {
|
|
415
415
|
if (!canonical.present || !workPlan.present) {
|
|
416
416
|
return [];
|
|
417
417
|
}
|
|
418
|
+
const testDeclared = new Set(testDeclaredPaths);
|
|
418
419
|
const classification = (ownership, path) => ownership.toCreate.includes(path)
|
|
419
420
|
? 'Create'
|
|
420
421
|
: ownership.toModify.includes(path)
|
|
@@ -425,7 +426,7 @@ function validateOwnershipAgreement(canonical, workPlan) {
|
|
|
425
426
|
...canonical.toModify,
|
|
426
427
|
...workPlan.toCreate,
|
|
427
428
|
...workPlan.toModify,
|
|
428
|
-
]);
|
|
429
|
+
].filter((path) => !testDeclared.has(path)));
|
|
429
430
|
return [...paths].sort().flatMap((path) => {
|
|
430
431
|
const canonicalClass = classification(canonical, path);
|
|
431
432
|
const workPlanClass = classification(workPlan, path);
|
|
@@ -598,7 +599,7 @@ export async function packageHandoff(spec, knowledge) {
|
|
|
598
599
|
...canonicalFiles.blockers,
|
|
599
600
|
...canonicalOwnership.blockers,
|
|
600
601
|
...workPlanOwnership.blockers,
|
|
601
|
-
...validateOwnershipAgreement(canonicalOwnership, workPlanOwnership),
|
|
602
|
+
...validateOwnershipAgreement(canonicalOwnership, workPlanOwnership, canonicalFiles.toTestDeclared),
|
|
602
603
|
...(!['draft', 'review', 'approved', 'implementing', 'done', 'discarded'].includes(spec.status)
|
|
603
604
|
? [`UNSUPPORTED_LIFECYCLE_STATUS: ${spec.status}`]
|
|
604
605
|
: []),
|
|
@@ -27,9 +27,9 @@ export declare function extractSpecIdFromCommit(projectPath: string, sha: string
|
|
|
27
27
|
*/
|
|
28
28
|
export declare function lookupSpecStatus(projectPath: string, specId: string): Promise<string | null>;
|
|
29
29
|
/**
|
|
30
|
-
* Pass 2:
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* Pass 2: a `not_merged` branch whose spec is done/discarded becomes `stale`
|
|
31
|
+
* only when `git merge-base --is-ancestor` proves every commit is reachable
|
|
32
|
+
* from main, develop, or master. Spec status alone never promotes a branch.
|
|
33
33
|
* Mutates both arrays in-place.
|
|
34
34
|
*/
|
|
35
35
|
export declare function applySpecDonePass(projectPath: string, skipped: SkippedBranchInfo[], stale: StaleBranchInfo[], branchShas: Map<string, string>): Promise<void>;
|
|
@@ -148,8 +148,6 @@ export async function extractSpecIdFromCommit(projectPath, sha) {
|
|
|
148
148
|
return null;
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
/** Terminal statuses that mean the branch work is complete. */
|
|
152
|
-
const DONE_STATUSES = new Set(['done', 'discarded']);
|
|
153
151
|
/**
|
|
154
152
|
* Look up the status of a spec by reading 'planu/specs/<specId>-star/spec.md' frontmatter.
|
|
155
153
|
* Returns the raw status string, or null when not found.
|
|
@@ -176,43 +174,41 @@ export async function lookupSpecStatus(projectPath, specId) {
|
|
|
176
174
|
return null;
|
|
177
175
|
}
|
|
178
176
|
}
|
|
177
|
+
async function isProvenIntegrated(projectPath, branch) {
|
|
178
|
+
for (const base of ['main', 'develop', 'master']) {
|
|
179
|
+
try {
|
|
180
|
+
await execFile('git', ['merge-base', '--is-ancestor', branch, base], { cwd: projectPath });
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
179
189
|
/**
|
|
180
|
-
* Pass 2:
|
|
181
|
-
*
|
|
182
|
-
*
|
|
190
|
+
* Pass 2: a `not_merged` branch whose spec is done/discarded becomes `stale`
|
|
191
|
+
* only when `git merge-base --is-ancestor` proves every commit is reachable
|
|
192
|
+
* from main, develop, or master. Spec status alone never promotes a branch.
|
|
183
193
|
* Mutates both arrays in-place.
|
|
184
194
|
*/
|
|
185
195
|
export async function applySpecDonePass(projectPath, skipped, stale, branchShas) {
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
const toRemove = new Set();
|
|
192
|
-
for (const branch of notMerged) {
|
|
193
|
-
// Try branch name first (zero git calls)
|
|
194
|
-
let specId = extractSpecIdFromName(branch.name);
|
|
195
|
-
// Fall back to commit message if name has no spec id (one git call per branch)
|
|
196
|
-
if (!specId) {
|
|
197
|
-
const sha = branchShas.get(branch.name) ?? '';
|
|
198
|
-
specId = await extractSpecIdFromCommit(projectPath, sha);
|
|
199
|
-
}
|
|
196
|
+
const candidates = skipped.filter((entry) => entry.skipReason === 'not_merged');
|
|
197
|
+
for (const candidate of candidates) {
|
|
198
|
+
const sha = branchShas.get(candidate.name) ?? '';
|
|
199
|
+
const specId = extractSpecIdFromName(candidate.name) ?? (await extractSpecIdFromCommit(projectPath, sha));
|
|
200
200
|
if (!specId) {
|
|
201
201
|
continue;
|
|
202
202
|
}
|
|
203
203
|
const status = await lookupSpecStatus(projectPath, specId);
|
|
204
|
-
if (status !==
|
|
205
|
-
|
|
206
|
-
stale.push({ name: branch.name, sha, reason: 'spec-done' });
|
|
207
|
-
toRemove.add(branch.name);
|
|
204
|
+
if (status !== 'done' && status !== 'discarded') {
|
|
205
|
+
continue;
|
|
208
206
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
for (let i = skipped.length - 1; i >= 0; i--) {
|
|
212
|
-
const entry = skipped[i];
|
|
213
|
-
if (entry && toRemove.has(entry.name)) {
|
|
214
|
-
skipped.splice(i, 1);
|
|
207
|
+
if (!(await isProvenIntegrated(projectPath, candidate.name))) {
|
|
208
|
+
continue;
|
|
215
209
|
}
|
|
210
|
+
skipped.splice(skipped.indexOf(candidate), 1);
|
|
211
|
+
stale.push({ name: candidate.name, sha, reason: 'spec-done' });
|
|
216
212
|
}
|
|
217
213
|
}
|
|
218
214
|
export async function findStaleBranches(input) {
|
|
@@ -19,9 +19,43 @@ async function deleteBranch(projectPath, branchName) {
|
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
async function remoteBranchRefExists(projectPath, branchName) {
|
|
23
|
+
try {
|
|
24
|
+
const { stdout } = await execFile('git', ['ls-remote', '--heads', 'origin', branchName], {
|
|
25
|
+
cwd: projectPath,
|
|
26
|
+
});
|
|
27
|
+
return stdout.trim().length > 0;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function isRemoteBranchProvenMerged(projectPath, branchName) {
|
|
34
|
+
for (const base of ['main', 'develop', 'master']) {
|
|
35
|
+
try {
|
|
36
|
+
await execFile('git', ['merge-base', '--is-ancestor', `origin/${branchName}`, base], {
|
|
37
|
+
cwd: projectPath,
|
|
38
|
+
});
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
async function deleteRemoteBranch(projectPath, branchName) {
|
|
48
|
+
try {
|
|
49
|
+
await execFile('git', ['push', 'origin', '--delete', branchName], { cwd: projectPath });
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
22
56
|
async function removeWorktree(projectPath, worktreePath) {
|
|
23
57
|
try {
|
|
24
|
-
await execFile('git', ['worktree', 'remove',
|
|
58
|
+
await execFile('git', ['worktree', 'remove', worktreePath], { cwd: projectPath });
|
|
25
59
|
return true;
|
|
26
60
|
}
|
|
27
61
|
catch {
|
|
@@ -41,7 +75,6 @@ async function dropStash(projectPath, stashRef) {
|
|
|
41
75
|
// Backup helpers
|
|
42
76
|
// ---------------------------------------------------------------------------
|
|
43
77
|
async function backupAndDeleteBranch(projectPath, branch) {
|
|
44
|
-
// Write to history BEFORE deleting (for recovery)
|
|
45
78
|
try {
|
|
46
79
|
await appendHousekeepingEntry(projectPath, {
|
|
47
80
|
projectPath,
|
|
@@ -50,10 +83,30 @@ async function backupAndDeleteBranch(projectPath, branch) {
|
|
|
50
83
|
sha: branch.sha,
|
|
51
84
|
});
|
|
52
85
|
}
|
|
53
|
-
catch {
|
|
54
|
-
|
|
86
|
+
catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
error: `branch ${branch.name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
90
|
+
};
|
|
55
91
|
}
|
|
56
|
-
return deleteBranch(projectPath, branch.name);
|
|
92
|
+
return { ok: await deleteBranch(projectPath, branch.name) };
|
|
93
|
+
}
|
|
94
|
+
async function backupAndDeleteRemoteBranch(projectPath, branch) {
|
|
95
|
+
try {
|
|
96
|
+
await appendHousekeepingEntry(projectPath, {
|
|
97
|
+
projectPath,
|
|
98
|
+
kind: 'branch',
|
|
99
|
+
ref: `origin/${branch.name}`,
|
|
100
|
+
sha: branch.sha,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
error: `remote branch origin/${branch.name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return { ok: await deleteRemoteBranch(projectPath, branch.name) };
|
|
57
110
|
}
|
|
58
111
|
async function backupAndRemoveWorktree(projectPath, wt) {
|
|
59
112
|
try {
|
|
@@ -64,10 +117,13 @@ async function backupAndRemoveWorktree(projectPath, wt) {
|
|
|
64
117
|
sha: wt.branch ?? '',
|
|
65
118
|
});
|
|
66
119
|
}
|
|
67
|
-
catch {
|
|
68
|
-
|
|
120
|
+
catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
error: `worktree ${wt.path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
124
|
+
};
|
|
69
125
|
}
|
|
70
|
-
return removeWorktree(projectPath, wt.path);
|
|
126
|
+
return { ok: await removeWorktree(projectPath, wt.path) };
|
|
71
127
|
}
|
|
72
128
|
async function backupAndDropStash(projectPath, stash) {
|
|
73
129
|
try {
|
|
@@ -78,16 +134,19 @@ async function backupAndDropStash(projectPath, stash) {
|
|
|
78
134
|
sha: String(stash.createdAt),
|
|
79
135
|
});
|
|
80
136
|
}
|
|
81
|
-
catch {
|
|
82
|
-
|
|
137
|
+
catch (error) {
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
error: `stash ${stash.ref}: ${error instanceof Error ? error.message : String(error)}`,
|
|
141
|
+
};
|
|
83
142
|
}
|
|
84
|
-
return dropStash(projectPath, stash.ref);
|
|
143
|
+
return { ok: await dropStash(projectPath, stash.ref) };
|
|
85
144
|
}
|
|
86
145
|
// ---------------------------------------------------------------------------
|
|
87
146
|
// Main orchestrator
|
|
88
147
|
// ---------------------------------------------------------------------------
|
|
89
148
|
export async function runHousekeepingSweep(input) {
|
|
90
|
-
const { projectPath,
|
|
149
|
+
const { projectPath, authority = { mode: 'report' }, aggressive = false, includeStashes = false, } = input;
|
|
91
150
|
const sweepAt = new Date().toISOString();
|
|
92
151
|
// Find stale items in parallel
|
|
93
152
|
const [{ stale: staleBranches, skipped }, staleWorktrees, staleStashes] = await Promise.all([
|
|
@@ -96,6 +155,7 @@ export async function runHousekeepingSweep(input) {
|
|
|
96
155
|
includeStashes ? findStaleStashes({ projectPath }) : Promise.resolve([]),
|
|
97
156
|
]);
|
|
98
157
|
const report = {
|
|
158
|
+
mode: authority.mode,
|
|
99
159
|
sweepAt,
|
|
100
160
|
projectPath,
|
|
101
161
|
wouldDelete: {
|
|
@@ -108,34 +168,60 @@ export async function runHousekeepingSweep(input) {
|
|
|
108
168
|
},
|
|
109
169
|
executed: false,
|
|
110
170
|
deleted: { branches: 0, worktrees: 0, stashes: 0, backups: 0 },
|
|
171
|
+
errors: [],
|
|
111
172
|
};
|
|
112
|
-
if (
|
|
173
|
+
if (authority.mode === 'report') {
|
|
113
174
|
return report;
|
|
114
175
|
}
|
|
115
176
|
// Execute deletions
|
|
116
177
|
report.executed = true;
|
|
117
178
|
// Delete branches (sequential — each needs a backup entry first)
|
|
118
179
|
for (const branch of staleBranches) {
|
|
119
|
-
const
|
|
120
|
-
if (ok) {
|
|
180
|
+
const result = await backupAndDeleteBranch(projectPath, branch);
|
|
181
|
+
if (result.ok) {
|
|
121
182
|
report.deleted.branches++;
|
|
122
183
|
}
|
|
184
|
+
else if (result.error) {
|
|
185
|
+
report.errors.push(result.error);
|
|
186
|
+
}
|
|
187
|
+
if (!authority.deleteRemoteRefs) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (!(await remoteBranchRefExists(projectPath, branch.name))) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (!(await isRemoteBranchProvenMerged(projectPath, branch.name))) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const remoteResult = await backupAndDeleteRemoteBranch(projectPath, branch);
|
|
197
|
+
if (remoteResult.ok) {
|
|
198
|
+
report.deleted.branches++;
|
|
199
|
+
}
|
|
200
|
+
else if (remoteResult.error) {
|
|
201
|
+
report.errors.push(remoteResult.error);
|
|
202
|
+
}
|
|
123
203
|
}
|
|
124
204
|
// Remove worktrees (sequential)
|
|
125
205
|
for (const wt of staleWorktrees) {
|
|
126
|
-
const
|
|
127
|
-
if (ok) {
|
|
206
|
+
const result = await backupAndRemoveWorktree(projectPath, wt);
|
|
207
|
+
if (result.ok) {
|
|
128
208
|
report.deleted.worktrees++;
|
|
129
209
|
}
|
|
210
|
+
else if (result.error) {
|
|
211
|
+
report.errors.push(result.error);
|
|
212
|
+
}
|
|
130
213
|
}
|
|
131
214
|
// Drop stashes (sequential — must handle index shifting)
|
|
132
215
|
// Drop in reverse order so indices stay valid
|
|
133
216
|
const reversedStashes = [...staleStashes].reverse();
|
|
134
217
|
for (const stash of reversedStashes) {
|
|
135
|
-
const
|
|
136
|
-
if (ok) {
|
|
218
|
+
const result = await backupAndDropStash(projectPath, stash);
|
|
219
|
+
if (result.ok) {
|
|
137
220
|
report.deleted.stashes++;
|
|
138
221
|
}
|
|
222
|
+
else if (result.error) {
|
|
223
|
+
report.errors.push(result.error);
|
|
224
|
+
}
|
|
139
225
|
}
|
|
140
226
|
// SPEC-771: clean stale .bak.* backup files from planu/specs/ (best-effort)
|
|
141
227
|
report.deleted.backups = await cleanStaleBackups(projectPath);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"cliVersion": "5.3.
|
|
4
|
-
"engineVersion": "5.3.
|
|
5
|
-
"buildId": "1-5.3.
|
|
3
|
+
"cliVersion": "5.3.15",
|
|
4
|
+
"engineVersion": "5.3.15",
|
|
5
|
+
"buildId": "1-5.3.15-aarch64-apple-darwin",
|
|
6
6
|
"protocolAbi": 1,
|
|
7
7
|
"capabilityAbi": 2,
|
|
8
8
|
"nodeApiAbi": 4,
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"platform": "darwin",
|
|
18
18
|
"arch": "arm64",
|
|
19
19
|
"libc": null,
|
|
20
|
-
"artifactSha256": "
|
|
20
|
+
"artifactSha256": "0fbd8c0babdbfb88cae442db9cc57521f67ee345aa1872ed0cd04f464f992584",
|
|
21
21
|
"sbom": {
|
|
22
22
|
"format": "CycloneDX-1.5",
|
|
23
23
|
"path": "planu-core.darwin-arm64.node.sbom.json",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "41999cac08a7c64a9c899eac628df8e4bc10d70dc1b1923d5951a89f5ac8e878"
|
|
25
25
|
},
|
|
26
26
|
"provenance": {
|
|
27
27
|
"builder": "scripts/build-rust-local.sh",
|
|
28
|
-
"sourceCommit": "
|
|
28
|
+
"sourceCommit": "14948c630bfb7a355391c10563934cc79e8bb167",
|
|
29
29
|
"sourceTreeDirty": false,
|
|
30
30
|
"sourceDateEpoch": 1,
|
|
31
31
|
"rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
"signature": {
|
|
35
35
|
"algorithm": "Ed25519",
|
|
36
36
|
"keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
|
|
37
|
-
"value": "
|
|
37
|
+
"value": "bTat4AK64vTOtmTHrAf9w8z5d8GbFuxJZizc1YuZm05VpDaKzSaHkztZ5Hrb+sKu0lppYebpOGwYqTcFmfUMDQ=="
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"version": 1,
|
|
5
5
|
"metadata": {
|
|
6
6
|
"component": {
|
|
7
|
-
"bom-ref": "pkg:cargo/planu-core@5.3.
|
|
7
|
+
"bom-ref": "pkg:cargo/planu-core@5.3.15",
|
|
8
8
|
"type": "library",
|
|
9
9
|
"name": "planu-core",
|
|
10
|
-
"version": "5.3.
|
|
10
|
+
"version": "5.3.15",
|
|
11
11
|
"hashes": [
|
|
12
12
|
{
|
|
13
13
|
"alg": "SHA-256",
|
|
14
|
-
"content": "
|
|
14
|
+
"content": "0fbd8c0babdbfb88cae442db9cc57521f67ee345aa1872ed0cd04f464f992584"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"type": "library",
|
|
73
|
-
"bom-ref": "pkg:cargo/bstr@1.13.
|
|
73
|
+
"bom-ref": "pkg:cargo/bstr@1.13.1",
|
|
74
74
|
"name": "bstr",
|
|
75
|
-
"version": "1.13.
|
|
75
|
+
"version": "1.13.1",
|
|
76
76
|
"externalReferences": [
|
|
77
77
|
{
|
|
78
78
|
"type": "distribution",
|
|
@@ -971,7 +971,7 @@
|
|
|
971
971
|
]
|
|
972
972
|
},
|
|
973
973
|
{
|
|
974
|
-
"ref": "pkg:cargo/bstr@1.13.
|
|
974
|
+
"ref": "pkg:cargo/bstr@1.13.1",
|
|
975
975
|
"dependsOn": [
|
|
976
976
|
"pkg:cargo/memchr@2.8.3",
|
|
977
977
|
"pkg:cargo/serde_core@1.0.228"
|
|
@@ -1049,7 +1049,7 @@
|
|
|
1049
1049
|
"ref": "pkg:cargo/globset@0.4.20",
|
|
1050
1050
|
"dependsOn": [
|
|
1051
1051
|
"pkg:cargo/aho-corasick@1.1.5",
|
|
1052
|
-
"pkg:cargo/bstr@1.13.
|
|
1052
|
+
"pkg:cargo/bstr@1.13.1",
|
|
1053
1053
|
"pkg:cargo/log@0.4.33",
|
|
1054
1054
|
"pkg:cargo/regex-automata@0.4.18",
|
|
1055
1055
|
"pkg:cargo/regex-syntax@0.8.11"
|
|
@@ -1106,7 +1106,7 @@
|
|
|
1106
1106
|
"dependsOn": []
|
|
1107
1107
|
},
|
|
1108
1108
|
{
|
|
1109
|
-
"ref": "pkg:cargo/planu-core@5.3.
|
|
1109
|
+
"ref": "pkg:cargo/planu-core@5.3.15",
|
|
1110
1110
|
"dependsOn": [
|
|
1111
1111
|
"pkg:cargo/core-foundation@0.10.1",
|
|
1112
1112
|
"pkg:cargo/hmac@0.12.1",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"cliVersion": "5.3.
|
|
4
|
-
"engineVersion": "5.3.
|
|
5
|
-
"buildId": "1-5.3.
|
|
3
|
+
"cliVersion": "5.3.15",
|
|
4
|
+
"engineVersion": "5.3.15",
|
|
5
|
+
"buildId": "1-5.3.15-x86_64-apple-darwin",
|
|
6
6
|
"protocolAbi": 1,
|
|
7
7
|
"capabilityAbi": 2,
|
|
8
8
|
"nodeApiAbi": 4,
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"platform": "darwin",
|
|
18
18
|
"arch": "x64",
|
|
19
19
|
"libc": null,
|
|
20
|
-
"artifactSha256": "
|
|
20
|
+
"artifactSha256": "f2b7904e0e93116b6042e9f67e33ca60f2f054a48ae9f365327209473010a38a",
|
|
21
21
|
"sbom": {
|
|
22
22
|
"format": "CycloneDX-1.5",
|
|
23
23
|
"path": "planu-core.darwin-x64.node.sbom.json",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "40bb12bea402c3d433ca284cbb80c122ffcacb68defa17409da65583f9fe0b73"
|
|
25
25
|
},
|
|
26
26
|
"provenance": {
|
|
27
27
|
"builder": "scripts/build-rust-local.sh",
|
|
28
|
-
"sourceCommit": "
|
|
28
|
+
"sourceCommit": "14948c630bfb7a355391c10563934cc79e8bb167",
|
|
29
29
|
"sourceTreeDirty": false,
|
|
30
30
|
"sourceDateEpoch": 1,
|
|
31
31
|
"rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
"signature": {
|
|
35
35
|
"algorithm": "Ed25519",
|
|
36
36
|
"keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
|
|
37
|
-
"value": "
|
|
37
|
+
"value": "wDC/VmuwtNH7DIZHFeD9JAuHp08V8yDMSOgopr83IQIBnQkWBsqS9lQ2Zchxh7JNiTEfICvSQF7lMxA9Dk6UAQ=="
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"version": 1,
|
|
5
5
|
"metadata": {
|
|
6
6
|
"component": {
|
|
7
|
-
"bom-ref": "pkg:cargo/planu-core@5.3.
|
|
7
|
+
"bom-ref": "pkg:cargo/planu-core@5.3.15",
|
|
8
8
|
"type": "library",
|
|
9
9
|
"name": "planu-core",
|
|
10
|
-
"version": "5.3.
|
|
10
|
+
"version": "5.3.15",
|
|
11
11
|
"hashes": [
|
|
12
12
|
{
|
|
13
13
|
"alg": "SHA-256",
|
|
14
|
-
"content": "
|
|
14
|
+
"content": "f2b7904e0e93116b6042e9f67e33ca60f2f054a48ae9f365327209473010a38a"
|
|
15
15
|
}
|
|
16
16
|
]
|
|
17
17
|
}
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"type": "library",
|
|
73
|
-
"bom-ref": "pkg:cargo/bstr@1.13.
|
|
73
|
+
"bom-ref": "pkg:cargo/bstr@1.13.1",
|
|
74
74
|
"name": "bstr",
|
|
75
|
-
"version": "1.13.
|
|
75
|
+
"version": "1.13.1",
|
|
76
76
|
"externalReferences": [
|
|
77
77
|
{
|
|
78
78
|
"type": "distribution",
|
|
@@ -971,7 +971,7 @@
|
|
|
971
971
|
]
|
|
972
972
|
},
|
|
973
973
|
{
|
|
974
|
-
"ref": "pkg:cargo/bstr@1.13.
|
|
974
|
+
"ref": "pkg:cargo/bstr@1.13.1",
|
|
975
975
|
"dependsOn": [
|
|
976
976
|
"pkg:cargo/memchr@2.8.3",
|
|
977
977
|
"pkg:cargo/serde_core@1.0.228"
|
|
@@ -1049,7 +1049,7 @@
|
|
|
1049
1049
|
"ref": "pkg:cargo/globset@0.4.20",
|
|
1050
1050
|
"dependsOn": [
|
|
1051
1051
|
"pkg:cargo/aho-corasick@1.1.5",
|
|
1052
|
-
"pkg:cargo/bstr@1.13.
|
|
1052
|
+
"pkg:cargo/bstr@1.13.1",
|
|
1053
1053
|
"pkg:cargo/log@0.4.33",
|
|
1054
1054
|
"pkg:cargo/regex-automata@0.4.18",
|
|
1055
1055
|
"pkg:cargo/regex-syntax@0.8.11"
|
|
@@ -1106,7 +1106,7 @@
|
|
|
1106
1106
|
"dependsOn": []
|
|
1107
1107
|
},
|
|
1108
1108
|
{
|
|
1109
|
-
"ref": "pkg:cargo/planu-core@5.3.
|
|
1109
|
+
"ref": "pkg:cargo/planu-core@5.3.15",
|
|
1110
1110
|
"dependsOn": [
|
|
1111
1111
|
"pkg:cargo/core-foundation@0.10.1",
|
|
1112
1112
|
"pkg:cargo/hmac@0.12.1",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"cliVersion": "5.3.
|
|
4
|
-
"engineVersion": "5.3.
|
|
5
|
-
"buildId": "1-5.3.
|
|
3
|
+
"cliVersion": "5.3.15",
|
|
4
|
+
"engineVersion": "5.3.15",
|
|
5
|
+
"buildId": "1-5.3.15-aarch64-unknown-linux-gnu",
|
|
6
6
|
"protocolAbi": 1,
|
|
7
7
|
"capabilityAbi": 2,
|
|
8
8
|
"nodeApiAbi": 4,
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"platform": "linux",
|
|
17
17
|
"arch": "arm64",
|
|
18
18
|
"libc": "glibc",
|
|
19
|
-
"artifactSha256": "
|
|
19
|
+
"artifactSha256": "01ea24db6f97e97faeef630279a6ee999acb4a7562771459a2423a7598304253",
|
|
20
20
|
"sbom": {
|
|
21
21
|
"format": "CycloneDX-1.5",
|
|
22
22
|
"path": "planu-core.linux-arm64-gnu.node.sbom.json",
|
|
23
|
-
"sha256": "
|
|
23
|
+
"sha256": "02f9bcacd4b15056f3b945e103ed330d231fc9ac0f1a377dd1ec326e60b2b1dc"
|
|
24
24
|
},
|
|
25
25
|
"provenance": {
|
|
26
26
|
"builder": "scripts/build-rust-local.sh",
|
|
27
|
-
"sourceCommit": "
|
|
27
|
+
"sourceCommit": "14948c630bfb7a355391c10563934cc79e8bb167",
|
|
28
28
|
"sourceTreeDirty": false,
|
|
29
29
|
"sourceDateEpoch": 1,
|
|
30
30
|
"rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
"signature": {
|
|
34
34
|
"algorithm": "Ed25519",
|
|
35
35
|
"keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
|
|
36
|
-
"value": "
|
|
36
|
+
"value": "LwJ+lJNVWcGsacrsWvGRbBS6WQTC7iMOAg3yfb0AZtdsHLBvAyG1700kh/1E1CbKEUR73CbUmU2TzEzVkesUAw=="
|
|
37
37
|
}
|
|
38
38
|
}
|