@bigking67/pi-67 0.10.13 → 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,12 @@
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
+
3
10
  ## [0.10.13]
4
11
 
5
12
  - Normalizes `settings.json` BOM/CRLF line endings during `update --repair`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigking67/pi-67",
3
- "version": "0.10.13",
3
+ "version": "0.10.14",
4
4
  "description": "Cross-platform manager CLI for the pi-67 Pi Coding Agent distribution.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/check.mjs CHANGED
@@ -11,6 +11,7 @@ import { buildPlanDecisions, classifyGitShort } from "../src/lib/update-plan.mjs
11
11
  import {
12
12
  migrateSettingsRuntimeState,
13
13
  mergeSettingsRuntimeMarkerIntoState,
14
+ refreshSettingsGitIndex,
14
15
  settingsRuntimeMarkerFromObject,
15
16
  stripSettingsRuntimeMarkerText,
16
17
  } from "../src/lib/settings-runtime-state.mjs";
@@ -428,6 +429,33 @@ function runSettingsRuntimeStateSelfTests() {
428
429
  "settings line-ending normalization must preserve JSON content and indentation",
429
430
  );
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
+ }
431
459
  }
432
460
 
433
461
  function runUpdatePreflightMigrationSelfTests() {
@@ -60,6 +60,9 @@ export async function installCommand(ctx, argv) {
60
60
  if (runtimeState.settingsNormalized) {
61
61
  info("Normalized settings.json runtime marker/line endings.");
62
62
  }
63
+ if (runtimeState.gitIndexRefreshed) {
64
+ info("Refreshed settings.json Git index stat cache.");
65
+ }
63
66
  if (runtimeState.gitFilterInstalled) {
64
67
  info("Installed local git clean filter for future settings.json runtime markers.");
65
68
  }
@@ -102,6 +102,9 @@ function reportSettingsRuntimeStateMigration(ctx, options = {}) {
102
102
  if (result.settingsNormalized) {
103
103
  info(`${phase}${dryRun ? "would normalize" : "Normalized"} settings.json runtime marker/line endings.`);
104
104
  }
105
+ if (result.gitIndexRefreshed) {
106
+ info(`${phase}${dryRun ? "would refresh" : "Refreshed"} settings.json Git index stat cache.`);
107
+ }
105
108
  if (result.gitFilterInstalled) {
106
109
  info(`${phase}${dryRun ? "would install" : "Installed"} local git clean filter for future settings.json runtime markers.`);
107
110
  }
@@ -68,6 +68,8 @@ export function migrateSettingsRuntimeState(ctx, options = {}) {
68
68
  stateWritten: false,
69
69
  settingsNormalized: false,
70
70
  settingsNormalizeReasons: [],
71
+ gitIndexRefreshed: false,
72
+ gitIndexRefreshSkipped: "",
71
73
  gitFilterInstalled: false,
72
74
  skipped: [],
73
75
  errors: [],
@@ -131,6 +133,13 @@ export function migrateSettingsRuntimeState(ctx, options = {}) {
131
133
  if (filterResult.error) result.errors.push(filterResult.error);
132
134
  }
133
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
+
134
143
  return result;
135
144
  }
136
145
 
@@ -140,6 +149,63 @@ export function normalizeSettingsTextLineEndings(text) {
140
149
  .replace(/\r\n?/g, "\n");
141
150
  }
142
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
+ }
206
+ return result;
207
+ }
208
+
143
209
  export function installSettingsRuntimeGitFilter(ctx, options = {}) {
144
210
  const result = {
145
211
  installed: false,
@@ -53,6 +53,9 @@ function printHuman(result) {
53
53
  if (result.settingsNormalized) {
54
54
  console.log("PASS normalized settings.json runtime marker/line endings");
55
55
  }
56
+ if (result.gitIndexRefreshed) {
57
+ console.log("PASS refreshed settings.json Git index stat cache");
58
+ }
56
59
  if (result.gitFilterInstalled) {
57
60
  console.log("PASS installed local git clean filter for settings.json runtime marker");
58
61
  }