@bigking67/pi-67 0.10.12 → 0.10.14
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
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.14]
|
|
4
|
+
|
|
5
|
+
- Refreshes the Git index stat cache for `settings.json` during
|
|
6
|
+
`update --repair` when Git reports `M settings.json` but `git diff` has no
|
|
7
|
+
real content changes, clearing the remaining Windows false-dirty state after
|
|
8
|
+
line-ending/runtime-state cleanup.
|
|
9
|
+
|
|
10
|
+
## [0.10.13]
|
|
11
|
+
|
|
12
|
+
- Normalizes `settings.json` BOM/CRLF line endings during `update --repair`
|
|
13
|
+
even when the runtime `lastChangelogVersion` marker is already absent,
|
|
14
|
+
clearing Windows Git EOL false-dirty states without changing provider,
|
|
15
|
+
model, theme, or other JSON content.
|
|
16
|
+
|
|
3
17
|
## [0.10.12]
|
|
4
18
|
|
|
5
19
|
- Pins `settings.json` to LF line endings in `.gitattributes` while preserving
|
package/package.json
CHANGED
package/scripts/check.mjs
CHANGED
|
@@ -9,7 +9,9 @@ import { parseCommandOptions, splitGlobalArgs } from "../src/lib/args.mjs";
|
|
|
9
9
|
import { readExtensionRegistry, validateExtensionRegistry } from "../src/lib/extension-registry.mjs";
|
|
10
10
|
import { buildPlanDecisions, classifyGitShort } from "../src/lib/update-plan.mjs";
|
|
11
11
|
import {
|
|
12
|
+
migrateSettingsRuntimeState,
|
|
12
13
|
mergeSettingsRuntimeMarkerIntoState,
|
|
14
|
+
refreshSettingsGitIndex,
|
|
13
15
|
settingsRuntimeMarkerFromObject,
|
|
14
16
|
stripSettingsRuntimeMarkerText,
|
|
15
17
|
} from "../src/lib/settings-runtime-state.mjs";
|
|
@@ -406,6 +408,54 @@ function runSettingsRuntimeStateSelfTests() {
|
|
|
406
408
|
state.runtimeMarkers.lastChangelogVersion.storage === "state.json",
|
|
407
409
|
"settings runtime marker must migrate into state runtimeMarkers",
|
|
408
410
|
);
|
|
411
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "pi67-settings-runtime-state-"));
|
|
412
|
+
const agentDir = path.join(tmpRoot, "agent");
|
|
413
|
+
const stateDir = path.join(tmpRoot, "state");
|
|
414
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
415
|
+
fs.writeFileSync(path.join(agentDir, "settings.json"), "{\r\n \"theme\": \"gruvbox-dark\"\r\n}\r\n", "utf8");
|
|
416
|
+
const lineEndingResult = migrateSettingsRuntimeState(
|
|
417
|
+
{ agentDir, repoRoot: agentDir, stateDir },
|
|
418
|
+
{ normalizeSettingsJson: true },
|
|
419
|
+
);
|
|
420
|
+
const normalizedSettings = fs.readFileSync(path.join(agentDir, "settings.json"), "utf8");
|
|
421
|
+
assert(lineEndingResult.markerFound === false, "line-ending normalization must not require runtime marker");
|
|
422
|
+
assert(lineEndingResult.settingsNormalized === true, "CRLF settings.json must be normalized under --normalize");
|
|
423
|
+
assert(
|
|
424
|
+
lineEndingResult.settingsNormalizeReasons.includes("line-endings"),
|
|
425
|
+
"settings normalization must classify CRLF as line-endings",
|
|
426
|
+
);
|
|
427
|
+
assert(
|
|
428
|
+
normalizedSettings === "{\n \"theme\": \"gruvbox-dark\"\n}\n",
|
|
429
|
+
"settings line-ending normalization must preserve JSON content and indentation",
|
|
430
|
+
);
|
|
431
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
432
|
+
|
|
433
|
+
if (spawnSync("git", ["--version"], { encoding: "utf8" }).status === 0) {
|
|
434
|
+
const gitRoot = fs.mkdtempSync(path.join(os.tmpdir(), "pi67-settings-index-refresh-"));
|
|
435
|
+
const gitStateDir = path.join(gitRoot, "state");
|
|
436
|
+
fs.writeFileSync(path.join(gitRoot, ".gitattributes"), "settings.json text eol=lf\n");
|
|
437
|
+
fs.writeFileSync(path.join(gitRoot, "settings.json"), "{\n \"theme\": \"gruvbox-dark\"\n}\n");
|
|
438
|
+
for (const args of [
|
|
439
|
+
["-C", gitRoot, "init", "-q"],
|
|
440
|
+
["-C", gitRoot, "config", "user.email", "pi67-check@example.invalid"],
|
|
441
|
+
["-C", gitRoot, "config", "user.name", "pi67-check"],
|
|
442
|
+
["-C", gitRoot, "add", ".gitattributes", "settings.json"],
|
|
443
|
+
["-C", gitRoot, "commit", "-q", "-m", "init"],
|
|
444
|
+
]) {
|
|
445
|
+
const result = spawnSync("git", args, { encoding: "utf8" });
|
|
446
|
+
assert(result.status === 0, `git setup failed for settings index refresh self-test: git ${args.join(" ")}\n${result.stderr}`);
|
|
447
|
+
}
|
|
448
|
+
fs.writeFileSync(path.join(gitRoot, "settings.json"), "{\r\n \"theme\": \"gruvbox-dark\"\r\n}\r\n");
|
|
449
|
+
const statusBefore = spawnSync("git", ["-C", gitRoot, "status", "--short", "--", "settings.json"], { encoding: "utf8" });
|
|
450
|
+
const diffBefore = spawnSync("git", ["-C", gitRoot, "diff", "--quiet", "--", "settings.json"], { encoding: "utf8" });
|
|
451
|
+
assert(statusBefore.stdout.includes("settings.json"), "settings index refresh self-test must start with false-dirty status");
|
|
452
|
+
assert(diffBefore.status === 0, "settings index refresh self-test must have no real content diff");
|
|
453
|
+
const refreshResult = refreshSettingsGitIndex({ agentDir: gitRoot, repoRoot: gitRoot, stateDir: gitStateDir });
|
|
454
|
+
const statusAfter = spawnSync("git", ["-C", gitRoot, "status", "--short", "--", "settings.json"], { encoding: "utf8" });
|
|
455
|
+
assert(refreshResult.refreshed === true, "settings index refresh must classify cleared false-dirty status");
|
|
456
|
+
assert(!statusAfter.stdout.trim(), `settings index refresh must clear false-dirty status\n${statusAfter.stdout}`);
|
|
457
|
+
fs.rmSync(gitRoot, { recursive: true, force: true });
|
|
458
|
+
}
|
|
409
459
|
}
|
|
410
460
|
|
|
411
461
|
function runUpdatePreflightMigrationSelfTests() {
|
package/src/commands/install.mjs
CHANGED
|
@@ -58,7 +58,10 @@ export async function installCommand(ctx, argv) {
|
|
|
58
58
|
info("Migrated settings.json lastChangelogVersion to ignored state: ~/.pi/pi67/state.json");
|
|
59
59
|
}
|
|
60
60
|
if (runtimeState.settingsNormalized) {
|
|
61
|
-
info("Normalized settings.json
|
|
61
|
+
info("Normalized settings.json runtime marker/line endings.");
|
|
62
|
+
}
|
|
63
|
+
if (runtimeState.gitIndexRefreshed) {
|
|
64
|
+
info("Refreshed settings.json Git index stat cache.");
|
|
62
65
|
}
|
|
63
66
|
if (runtimeState.gitFilterInstalled) {
|
|
64
67
|
info("Installed local git clean filter for future settings.json runtime markers.");
|
package/src/commands/update.mjs
CHANGED
|
@@ -100,7 +100,10 @@ function reportSettingsRuntimeStateMigration(ctx, options = {}) {
|
|
|
100
100
|
info(`${phase}${dryRun ? "would migrate" : "Migrated"} settings.json lastChangelogVersion to ignored state: ~/.pi/pi67/state.json`);
|
|
101
101
|
}
|
|
102
102
|
if (result.settingsNormalized) {
|
|
103
|
-
info(`${phase}${dryRun ? "would normalize" : "Normalized"} settings.json
|
|
103
|
+
info(`${phase}${dryRun ? "would normalize" : "Normalized"} settings.json runtime marker/line endings.`);
|
|
104
|
+
}
|
|
105
|
+
if (result.gitIndexRefreshed) {
|
|
106
|
+
info(`${phase}${dryRun ? "would refresh" : "Refreshed"} settings.json Git index stat cache.`);
|
|
104
107
|
}
|
|
105
108
|
if (result.gitFilterInstalled) {
|
|
106
109
|
info(`${phase}${dryRun ? "would install" : "Installed"} local git clean filter for future settings.json runtime markers.`);
|
|
@@ -67,6 +67,9 @@ export function migrateSettingsRuntimeState(ctx, options = {}) {
|
|
|
67
67
|
markerValue: "",
|
|
68
68
|
stateWritten: false,
|
|
69
69
|
settingsNormalized: false,
|
|
70
|
+
settingsNormalizeReasons: [],
|
|
71
|
+
gitIndexRefreshed: false,
|
|
72
|
+
gitIndexRefreshSkipped: "",
|
|
70
73
|
gitFilterInstalled: false,
|
|
71
74
|
skipped: [],
|
|
72
75
|
errors: [],
|
|
@@ -75,10 +78,12 @@ export function migrateSettingsRuntimeState(ctx, options = {}) {
|
|
|
75
78
|
if (!fs.existsSync(settingsPath)) {
|
|
76
79
|
result.skipped.push("settings.json missing");
|
|
77
80
|
} else {
|
|
81
|
+
let rawSettingsText = "";
|
|
78
82
|
let settingsText = "";
|
|
79
83
|
let settings = null;
|
|
80
84
|
try {
|
|
81
|
-
|
|
85
|
+
rawSettingsText = fs.readFileSync(settingsPath, "utf8");
|
|
86
|
+
settingsText = rawSettingsText.replace(/^\uFEFF/, "");
|
|
82
87
|
settings = JSON.parse(settingsText);
|
|
83
88
|
} catch (error) {
|
|
84
89
|
result.errors.push(`settings.json is not valid JSON: ${error.message}`);
|
|
@@ -95,14 +100,28 @@ export function migrateSettingsRuntimeState(ctx, options = {}) {
|
|
|
95
100
|
}
|
|
96
101
|
if (normalizeSettingsJson) {
|
|
97
102
|
const normalizedText = stableSettingsJson(stripSettingsRuntimeMarker(settings));
|
|
98
|
-
if (normalizedText !==
|
|
103
|
+
if (normalizedText !== rawSettingsText) {
|
|
99
104
|
if (!dryRun) {
|
|
100
105
|
fs.writeFileSync(settingsPath, normalizedText, "utf8");
|
|
101
106
|
}
|
|
102
107
|
result.settingsNormalized = true;
|
|
108
|
+
result.settingsNormalizeReasons.push("runtime-marker");
|
|
109
|
+
if (normalizeSettingsTextLineEndings(rawSettingsText) !== rawSettingsText) {
|
|
110
|
+
result.settingsNormalizeReasons.push("line-endings");
|
|
111
|
+
}
|
|
103
112
|
}
|
|
104
113
|
}
|
|
105
114
|
} else {
|
|
115
|
+
if (normalizeSettingsJson && settings) {
|
|
116
|
+
const normalizedText = normalizeSettingsTextLineEndings(rawSettingsText);
|
|
117
|
+
if (normalizedText !== rawSettingsText) {
|
|
118
|
+
if (!dryRun) {
|
|
119
|
+
fs.writeFileSync(settingsPath, normalizedText, "utf8");
|
|
120
|
+
}
|
|
121
|
+
result.settingsNormalized = true;
|
|
122
|
+
result.settingsNormalizeReasons.push("line-endings");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
106
125
|
result.skipped.push("settings.json runtime marker absent");
|
|
107
126
|
}
|
|
108
127
|
}
|
|
@@ -114,6 +133,76 @@ export function migrateSettingsRuntimeState(ctx, options = {}) {
|
|
|
114
133
|
if (filterResult.error) result.errors.push(filterResult.error);
|
|
115
134
|
}
|
|
116
135
|
|
|
136
|
+
if (normalizeSettingsJson) {
|
|
137
|
+
const refreshResult = refreshSettingsGitIndex(ctx, { dryRun });
|
|
138
|
+
result.gitIndexRefreshed = refreshResult.refreshed;
|
|
139
|
+
result.gitIndexRefreshSkipped = refreshResult.skipped;
|
|
140
|
+
if (refreshResult.error) result.errors.push(refreshResult.error);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function normalizeSettingsTextLineEndings(text) {
|
|
147
|
+
return String(text || "")
|
|
148
|
+
.replace(/^\uFEFF/, "")
|
|
149
|
+
.replace(/\r\n?/g, "\n");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function refreshSettingsGitIndex(ctx, options = {}) {
|
|
153
|
+
const result = {
|
|
154
|
+
refreshed: false,
|
|
155
|
+
skipped: "",
|
|
156
|
+
error: "",
|
|
157
|
+
};
|
|
158
|
+
if (options.dryRun) {
|
|
159
|
+
result.refreshed = true;
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
if (!isGitRepo(ctx.repoRoot)) {
|
|
163
|
+
result.skipped = "repo root is not a git checkout";
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const settingsPath = path.join(ctx.agentDir, "settings.json");
|
|
168
|
+
if (!fs.existsSync(settingsPath)) {
|
|
169
|
+
result.skipped = "settings.json missing";
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const relativePath = path.relative(ctx.repoRoot, settingsPath);
|
|
174
|
+
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
175
|
+
result.skipped = "settings.json is outside repo root";
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
const gitPath = relativePath.split(path.sep).join("/");
|
|
179
|
+
|
|
180
|
+
const statusBefore = captureCommand("git", ["-C", ctx.repoRoot, "status", "--short", "--", gitPath]);
|
|
181
|
+
if (!statusBefore.ok) {
|
|
182
|
+
result.skipped = "could not inspect settings.json git status before index refresh";
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
if (!statusBefore.stdout.trim()) {
|
|
186
|
+
result.skipped = "settings.json git index already fresh";
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const diffBefore = captureCommand("git", ["-C", ctx.repoRoot, "diff", "--quiet", "--", gitPath]);
|
|
191
|
+
if (diffBefore.status !== 0) {
|
|
192
|
+
result.skipped = "settings.json has real content diff; index refresh left it visible";
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const refresh = captureCommand("git", ["-C", ctx.repoRoot, "update-index", "--refresh", "--", gitPath]);
|
|
197
|
+
|
|
198
|
+
const statusAfter = captureCommand("git", ["-C", ctx.repoRoot, "status", "--short", "--", gitPath]);
|
|
199
|
+
if (statusAfter.ok && !statusAfter.stdout.trim()) {
|
|
200
|
+
result.refreshed = true;
|
|
201
|
+
} else if (!refresh.ok) {
|
|
202
|
+
result.error = refresh.stderr || refresh.error || "failed to refresh settings.json git index";
|
|
203
|
+
} else {
|
|
204
|
+
result.skipped = "settings.json still appears dirty after git index refresh";
|
|
205
|
+
}
|
|
117
206
|
return result;
|
|
118
207
|
}
|
|
119
208
|
|
|
@@ -51,7 +51,10 @@ function printHuman(result) {
|
|
|
51
51
|
console.log("PASS settings.json runtime marker is already absent");
|
|
52
52
|
}
|
|
53
53
|
if (result.settingsNormalized) {
|
|
54
|
-
console.log("PASS normalized settings.json
|
|
54
|
+
console.log("PASS normalized settings.json runtime marker/line endings");
|
|
55
|
+
}
|
|
56
|
+
if (result.gitIndexRefreshed) {
|
|
57
|
+
console.log("PASS refreshed settings.json Git index stat cache");
|
|
55
58
|
}
|
|
56
59
|
if (result.gitFilterInstalled) {
|
|
57
60
|
console.log("PASS installed local git clean filter for settings.json runtime marker");
|