@planu/cli 5.7.1 → 5.7.3
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 +14 -0
- package/dist/.planu-build.json +1 -1
- package/dist/engine/housekeeping/gitignore-carry.d.ts +4 -0
- package/dist/engine/housekeeping/gitignore-carry.js +48 -0
- package/dist/engine/housekeeping/history-log.js +16 -1
- package/dist/types/housekeeping.d.ts +4 -0
- package/package.json +1 -1
- package/planu-plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [5.7.3] - 2026-08-31
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
- fix(release): validate ledger shape before abort version derivation
|
|
5
|
+
- fix(release): fail-closed recovery ledger writer and tolerant abort for corrupted releaseVersion
|
|
6
|
+
- fix(build): report real git failure in provenance stamp and retry transient errors
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## [5.7.2] - 2026-08-31
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
- fix(housekeeping): carry gitignore rule when runtime migration relocates private files
|
|
13
|
+
|
|
14
|
+
|
|
1
15
|
## [5.7.1] - 2026-08-31
|
|
2
16
|
|
|
3
17
|
### Bug Fixes
|
package/dist/.planu-build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"schemaVersion":1,"commit":"
|
|
1
|
+
{"schemaVersion":1,"commit":"c4bce4cb95bcc9bf608918ec18bf3dd47c6b3f91"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { GitignoreCarryResult } from '../../types/housekeeping.js';
|
|
2
|
+
export declare function detectTrackedRelocation(projectPath: string, newRelPath: string): Promise<string | null>;
|
|
3
|
+
export declare function carryGitignoreRule(projectPath: string, oldRelPath: string, newRelPath: string): Promise<GitignoreCarryResult>;
|
|
4
|
+
//# sourceMappingURL=gitignore-carry.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { readFile, appendFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
async function isGitIgnored(projectPath, relPath) {
|
|
8
|
+
try {
|
|
9
|
+
await execFileAsync('git', ['check-ignore', '-q', relPath], { cwd: projectPath });
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function detectTrackedRelocation(projectPath, newRelPath) {
|
|
17
|
+
try {
|
|
18
|
+
await execFileAsync('git', ['ls-files', '--error-unmatch', newRelPath], { cwd: projectPath });
|
|
19
|
+
return `[Planu] ${newRelPath} is git-tracked but contains private runtime state; run: git rm --cached ${newRelPath}`;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function appendGitignoreRule(projectPath, relPath) {
|
|
26
|
+
const gitignorePath = join(projectPath, '.gitignore');
|
|
27
|
+
const existingContent = existsSync(gitignorePath) ? await readFile(gitignorePath, 'utf-8') : '';
|
|
28
|
+
const alreadyPresent = existingContent.split('\n').some((line) => line.trim() === relPath);
|
|
29
|
+
if (alreadyPresent) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
const prefix = existingContent.length > 0 && !existingContent.endsWith('\n') ? '\n' : '';
|
|
33
|
+
await appendFile(gitignorePath, `${prefix}${relPath}\n`, 'utf-8');
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
export async function carryGitignoreRule(projectPath, oldRelPath, newRelPath) {
|
|
37
|
+
const [oldIgnored, newIgnored] = await Promise.all([
|
|
38
|
+
isGitIgnored(projectPath, oldRelPath),
|
|
39
|
+
isGitIgnored(projectPath, newRelPath),
|
|
40
|
+
]);
|
|
41
|
+
let ruleAppended = false;
|
|
42
|
+
if (oldIgnored && !newIgnored) {
|
|
43
|
+
ruleAppended = await appendGitignoreRule(projectPath, newRelPath);
|
|
44
|
+
}
|
|
45
|
+
const trackedWarning = await detectTrackedRelocation(projectPath, newRelPath);
|
|
46
|
+
return { ruleAppended, trackedWarning };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=gitignore-carry.js.map
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
import { existsSync } from 'node:fs';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { appendFile, readFile, mkdir, rm, writeFile } from 'node:fs/promises';
|
|
7
|
-
import { dirname, join } from 'node:path';
|
|
7
|
+
import { dirname, join, relative, sep } from 'node:path';
|
|
8
8
|
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1699
|
|
9
9
|
import { projectRuntimeDir } from '../../storage/storage-layout.js';
|
|
10
|
+
import { carryGitignoreRule } from './gitignore-carry.js';
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Path helpers
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -35,10 +36,22 @@ function entryId(line) {
|
|
|
35
36
|
function jsonlLines(content) {
|
|
36
37
|
return content.split('\n').filter((line) => line.trim().length > 0);
|
|
37
38
|
}
|
|
39
|
+
function toPosixRelative(projectPath, absPath) {
|
|
40
|
+
return relative(projectPath, absPath).split(sep).join('/');
|
|
41
|
+
}
|
|
42
|
+
async function carryGitignoreAndWarn(projectPath, legacyPath, newPath) {
|
|
43
|
+
const result = await carryGitignoreRule(projectPath, toPosixRelative(projectPath, legacyPath), toPosixRelative(projectPath, newPath));
|
|
44
|
+
if (result.trackedWarning !== null) {
|
|
45
|
+
console.warn(result.trackedWarning);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
38
48
|
export async function migrateLegacyHousekeepingHistory(projectPath) {
|
|
39
49
|
const legacyPath = legacyHousekeepingHistoryPath(projectPath);
|
|
40
50
|
const newPath = housekeepingHistoryPath(projectPath);
|
|
41
51
|
if (!existsSync(legacyPath)) {
|
|
52
|
+
if (existsSync(newPath)) {
|
|
53
|
+
await carryGitignoreAndWarn(projectPath, legacyPath, newPath);
|
|
54
|
+
}
|
|
42
55
|
return null;
|
|
43
56
|
}
|
|
44
57
|
const legacyContent = await readFile(legacyPath, 'utf-8');
|
|
@@ -50,6 +63,7 @@ export async function migrateLegacyHousekeepingHistory(projectPath) {
|
|
|
50
63
|
throw new Error('[Planu] housekeeping history migration verification failed');
|
|
51
64
|
}
|
|
52
65
|
await rm(legacyPath, { force: true });
|
|
66
|
+
await carryGitignoreAndWarn(projectPath, legacyPath, newPath);
|
|
53
67
|
return legacyPath;
|
|
54
68
|
}
|
|
55
69
|
const destinationContent = await readFile(newPath, 'utf-8');
|
|
@@ -62,6 +76,7 @@ export async function migrateLegacyHousekeepingHistory(projectPath) {
|
|
|
62
76
|
await appendFile(newPath, missingLines.map((line) => `${line}\n`).join(''), 'utf-8');
|
|
63
77
|
}
|
|
64
78
|
await rm(legacyPath, { force: true });
|
|
79
|
+
await carryGitignoreAndWarn(projectPath, legacyPath, newPath);
|
|
65
80
|
return legacyPath;
|
|
66
81
|
}
|
|
67
82
|
// ---------------------------------------------------------------------------
|
|
@@ -184,4 +184,8 @@ export interface LegacyPlanuSweepResult {
|
|
|
184
184
|
failures: LegacyPlanuDemolitionFailure[];
|
|
185
185
|
freedBytes: number;
|
|
186
186
|
}
|
|
187
|
+
export interface GitignoreCarryResult {
|
|
188
|
+
ruleAppended: boolean;
|
|
189
|
+
trackedWarning: string | null;
|
|
190
|
+
}
|
|
187
191
|
//# sourceMappingURL=housekeeping.d.ts.map
|
package/package.json
CHANGED
package/planu-plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dev.planu.cli",
|
|
3
3
|
"displayName": "Planu — Spec Driven Development",
|
|
4
4
|
"description": "Manage software specs, estimations, and autonomous SDD workflows. Language-agnostic MCP server for Claude Code.",
|
|
5
|
-
"version": "5.7.
|
|
5
|
+
"version": "5.7.3",
|
|
6
6
|
"icon": "assets/plugin/icon.svg",
|
|
7
7
|
"command": ["npx", "@planu/cli@latest"],
|
|
8
8
|
"packageName": "@planu/cli",
|