@bigking67/pi-67 0.10.4 → 0.10.5
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 +22 -0
- package/README.md +10 -6
- package/package.json +1 -1
- package/scripts/check.mjs +12 -0
- package/src/commands/doctor.mjs +6 -1
- package/src/commands/update.mjs +6 -3
- package/src/commands/xtalpi.mjs +48 -0
- package/src/lib/update-plan.mjs +45 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.10.5]
|
|
4
|
+
|
|
5
|
+
- Uses `git fetch` plus `git merge --ff-only FETCH_HEAD` in Bash and
|
|
6
|
+
PowerShell updaters, allowing the updater to inspect incoming changed paths
|
|
7
|
+
before deciding whether dirty preserved runtime files need temporary cleanup.
|
|
8
|
+
- Keeps dirty runtime config in place without creating a backup when the remote
|
|
9
|
+
is already current or incoming changed paths do not touch those files.
|
|
10
|
+
- Reports `preserve-in-place-no-backup` from `pi-67 update --check` when a
|
|
11
|
+
dirty runtime marker is harmless and the remote already matches local HEAD.
|
|
12
|
+
- Updates docs to separate current `~/.pi/pi67/backups/` runtime backups from
|
|
13
|
+
legacy read-only `~/.pi/agent-backups/` conflict snapshots.
|
|
14
|
+
- Tightens release/smoke gates by checking README version drift, rejecting
|
|
15
|
+
simulated placeholder final answers, and requiring package metadata smoke
|
|
16
|
+
cases to include the real package version.
|
|
17
|
+
- Adds `pi-67 xtalpi run` and a Windows PowerShell xtalpi launcher. The stable
|
|
18
|
+
launcher defaults `PI_OBSERVATIONAL_MEMORY_PASSIVE=true` to keep post-final
|
|
19
|
+
observational-memory background writes from holding the main task lifecycle
|
|
20
|
+
open.
|
|
21
|
+
- Bounds `pi skill list` in doctor with POSIX
|
|
22
|
+
`--skill-list-timeout-seconds` and PowerShell `-SkillListTimeoutSeconds`.
|
|
23
|
+
- Removes legacy `xtalpi-compat` runtime path examples from bootstrap docs.
|
|
24
|
+
|
|
3
25
|
## [0.10.4]
|
|
4
26
|
|
|
5
27
|
- Makes all command-level `--help` paths side-effect free, including
|
package/README.md
CHANGED
|
@@ -77,13 +77,17 @@ Before a real `update` or `repair`, the npm manager builds the update plan,
|
|
|
77
77
|
blocks unsafe non-runtime dirty worktrees, and acquires
|
|
78
78
|
`~/.pi/pi67/locks/update.lock`. Runtime config backup/restore is delegated to
|
|
79
79
|
the Bash or PowerShell updater script only when an in-place checkout needs to
|
|
80
|
-
temporarily clear dirty preserved runtime files
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
temporarily clear dirty preserved runtime files. The updater fetches first,
|
|
81
|
+
compares incoming `HEAD..FETCH_HEAD` changed paths, and creates a runtime
|
|
82
|
+
snapshot only when the incoming update touches those dirty preserved files.
|
|
83
|
+
Those script-level snapshots live under
|
|
84
|
+
`~/.pi/pi67/backups/pre-update-runtime-*`.
|
|
85
|
+
This keeps `--help`, blocked update plans, already-up-to-date updates,
|
|
86
|
+
non-overlapping incoming updates, and the public `npx -y
|
|
83
87
|
@bigking67/pi-67@latest update --repair` orchestration path free of duplicate
|
|
84
|
-
manager-owned runtime backups. If
|
|
85
|
-
the script-level updater reuses it instead of
|
|
86
|
-
directory.
|
|
88
|
+
manager-owned runtime backups. If a backup is actually needed and an identical
|
|
89
|
+
runtime snapshot already exists, the script-level updater reuses it instead of
|
|
90
|
+
writing another timestamped directory.
|
|
87
91
|
|
|
88
92
|
Runtime backups are first-class CLI state:
|
|
89
93
|
|
package/package.json
CHANGED
package/scripts/check.mjs
CHANGED
|
@@ -347,6 +347,18 @@ function runUpdatePlanSelfTests() {
|
|
|
347
347
|
"dirty runtime config alone must not block the distro update plan",
|
|
348
348
|
);
|
|
349
349
|
|
|
350
|
+
const dirtyRuntimeRemoteCurrent = decisionsFixture({
|
|
351
|
+
git: { dirty: true, short: " M settings.json", commit: "abcdef123456" },
|
|
352
|
+
remote: { ok: true, commit: "abcdef1234567890" },
|
|
353
|
+
});
|
|
354
|
+
const currentRemoteAction = buildPlanDecisions(dirtyRuntimeRemoteCurrent).actions.find((item) => item.id === "user-runtime-config");
|
|
355
|
+
assert(
|
|
356
|
+
currentRemoteAction?.operation === "preserve-in-place-no-backup" &&
|
|
357
|
+
currentRemoteAction.createsNewBackup === false &&
|
|
358
|
+
currentRemoteAction.writes.length === 0,
|
|
359
|
+
"dirty runtime config must not plan a new backup when the remote already matches local HEAD",
|
|
360
|
+
);
|
|
361
|
+
|
|
350
362
|
const dirtyReadme = decisionsFixture({
|
|
351
363
|
git: { dirty: true, short: " M README.md" },
|
|
352
364
|
});
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { isWindows } from "../lib/platform.mjs";
|
|
|
5
5
|
export async function doctorCommand(ctx, argv) {
|
|
6
6
|
const { options } = parseCommandOptions(argv, {
|
|
7
7
|
bools: ["json", "quiet", "dry-run", "deep-mcp", "strict-shared-skills"],
|
|
8
|
-
strings: ["mcp-timeout-ms"],
|
|
8
|
+
strings: ["mcp-timeout-ms", "skill-list-timeout-seconds"],
|
|
9
9
|
});
|
|
10
10
|
if (options.help) {
|
|
11
11
|
printDoctorHelp();
|
|
@@ -19,6 +19,9 @@ export async function doctorCommand(ctx, argv) {
|
|
|
19
19
|
if (options.strictSharedSkills) args.push(isWindows() ? "-StrictSharedSkills" : "--strict-shared-skills");
|
|
20
20
|
if (!isWindows() && options.deepMcp) args.push("--deep-mcp");
|
|
21
21
|
if (!isWindows() && options.mcpTimeoutMs) args.push("--mcp-timeout-ms", options.mcpTimeoutMs);
|
|
22
|
+
if (options.skillListTimeoutSeconds) {
|
|
23
|
+
args.push(isWindows() ? "-SkillListTimeoutSeconds" : "--skill-list-timeout-seconds", options.skillListTimeoutSeconds);
|
|
24
|
+
}
|
|
22
25
|
runDistroScript(ctx, { sh: "pi67-doctor.sh", ps1: "pi67-doctor.ps1" }, args, {
|
|
23
26
|
dryRun: ctx.dryRun || options.dryRun,
|
|
24
27
|
});
|
|
@@ -35,6 +38,8 @@ Options:
|
|
|
35
38
|
--quiet Reduce human output where supported.
|
|
36
39
|
--deep-mcp Run deeper MCP probes on POSIX platforms.
|
|
37
40
|
--mcp-timeout-ms N Timeout for deep MCP probes on POSIX platforms.
|
|
41
|
+
--skill-list-timeout-seconds N
|
|
42
|
+
Timeout for pi skill list on POSIX platforms.
|
|
38
43
|
--strict-shared-skills Treat differing shared skills as blocking.
|
|
39
44
|
--dry-run Print the script invocation without running it.
|
|
40
45
|
|
package/src/commands/update.mjs
CHANGED
|
@@ -135,7 +135,9 @@ function printPlan(plan) {
|
|
|
135
135
|
if (plan.actions?.length > 0) {
|
|
136
136
|
section("Planned safe actions");
|
|
137
137
|
for (const action of plan.actions) {
|
|
138
|
-
|
|
138
|
+
const writes = action.writes?.length ? action.writes.join(", ") : "none";
|
|
139
|
+
const suffix = action.backupCondition ? `; backup=${action.backupCondition}` : "";
|
|
140
|
+
info(`${action.id}: ${action.operation}; writes=${writes}; preserves=${action.preserves.join(", ")}${suffix}`);
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
if (plan.blocked?.length > 0) {
|
|
@@ -174,8 +176,9 @@ Options:
|
|
|
174
176
|
|
|
175
177
|
Safety:
|
|
176
178
|
Runtime config backup/restore is owned by the platform updater script when
|
|
177
|
-
preserved runtime files
|
|
178
|
-
never creates a runtime backup for --help
|
|
179
|
+
dirty preserved runtime files overlap incoming changed paths. The npm manager
|
|
180
|
+
owns the update lock and never creates a runtime backup for --help, a blocked
|
|
181
|
+
update plan, or an already-up-to-date update.
|
|
179
182
|
|
|
180
183
|
Examples:
|
|
181
184
|
pi-67 update --check
|
package/src/commands/xtalpi.mjs
CHANGED
|
@@ -16,6 +16,7 @@ export async function xtalpiCommand(ctx, argv) {
|
|
|
16
16
|
if (sub === "trend") return trend(ctx, rest);
|
|
17
17
|
if (sub === "drift") return drift(ctx, rest);
|
|
18
18
|
if (sub === "stress") return stress(ctx, rest);
|
|
19
|
+
if (sub === "run") return run(ctx, rest);
|
|
19
20
|
throw new CliError(`unknown xtalpi command: ${sub}`, 2);
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -110,6 +111,47 @@ function stress(ctx, argv) {
|
|
|
110
111
|
return smoke(ctx, ["--profile", options.profile || "full-suite", ...(options.dryRun ? ["--dry-run"] : [])]);
|
|
111
112
|
}
|
|
112
113
|
|
|
114
|
+
function run(ctx, argv) {
|
|
115
|
+
const passthroughIndex = argv.indexOf("--");
|
|
116
|
+
const optionArgv = passthroughIndex === -1 ? argv : argv.slice(0, passthroughIndex);
|
|
117
|
+
const passthrough = passthroughIndex === -1 ? [] : argv.slice(passthroughIndex + 1);
|
|
118
|
+
const { options, positionals } = parseCommandOptions(optionArgv, {
|
|
119
|
+
strings: ["model", "provider"],
|
|
120
|
+
bools: ["dry-run", "no-passive-observational-memory"],
|
|
121
|
+
});
|
|
122
|
+
if (options.help) return printXtalpiHelp();
|
|
123
|
+
const provider = options.provider || "xtalpi-pi-tools";
|
|
124
|
+
const model = options.model || "deepseek-v4-pro";
|
|
125
|
+
const piArgs = [...positionals, ...passthrough];
|
|
126
|
+
const env = {};
|
|
127
|
+
if (options.noPassiveObservationalMemory) {
|
|
128
|
+
env.PI_OBSERVATIONAL_MEMORY_PASSIVE = "false";
|
|
129
|
+
}
|
|
130
|
+
if (isWindows()) {
|
|
131
|
+
const pwsh = findPowerShell();
|
|
132
|
+
if (!pwsh) throw new CliError("PowerShell executable not found");
|
|
133
|
+
const args = [
|
|
134
|
+
"-NoProfile",
|
|
135
|
+
"-ExecutionPolicy",
|
|
136
|
+
"Bypass",
|
|
137
|
+
"-File",
|
|
138
|
+
scriptPath(ctx, "pi67-xtalpi-pi-tools.ps1"),
|
|
139
|
+
"-Provider",
|
|
140
|
+
provider,
|
|
141
|
+
"-Model",
|
|
142
|
+
model,
|
|
143
|
+
...piArgs,
|
|
144
|
+
];
|
|
145
|
+
runCommand(pwsh, args, { cwd: ctx.repoRoot, dryRun: ctx.dryRun || options.dryRun, env });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
runCommand("bash", [scriptPath(ctx, "pi67-xtalpi-pi-tools.sh"), ...piArgs], {
|
|
149
|
+
cwd: ctx.repoRoot,
|
|
150
|
+
dryRun: ctx.dryRun || options.dryRun,
|
|
151
|
+
env: { PROVIDER: provider, MODEL: model, ...env },
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
113
155
|
function profileFromOptions(options) {
|
|
114
156
|
if (options.profile) return options.profile;
|
|
115
157
|
if (options.extensionLowRisk) return "extension-low-risk";
|
|
@@ -129,11 +171,16 @@ Usage:
|
|
|
129
171
|
pi-67 xtalpi trend [--limit N] [--profile NAME] [--json] [--out-dir DIR]
|
|
130
172
|
pi-67 xtalpi drift [--limit N] [--run-kind LIST] [--json] [--out-dir DIR]
|
|
131
173
|
pi-67 xtalpi stress --until-done
|
|
174
|
+
pi-67 xtalpi run [--provider ID] [--model NAME] [--no-passive-observational-memory] [-- <pi args>]
|
|
132
175
|
|
|
133
176
|
Notes:
|
|
134
177
|
xtalpi-pi-tools treats xtalpi as plain chat-completions transport. Pi local
|
|
135
178
|
code owns tool protocol parsing, validation, repair, retry classification,
|
|
136
179
|
tool execution, and smoke gates.
|
|
180
|
+
xtalpi run uses the stable launcher and defaults
|
|
181
|
+
PI_OBSERVATIONAL_MEMORY_PASSIVE=true so post-final background memory writes
|
|
182
|
+
cannot hold the main task lifecycle open. Pass --no-passive-observational-memory
|
|
183
|
+
only when you explicitly want pi-observational-memory to record after final.
|
|
137
184
|
xtalpi drift defaults to --run-kind full-suite so targeted one-off smoke
|
|
138
185
|
artifacts do not create expected case-set drift noise.
|
|
139
186
|
|
|
@@ -144,5 +191,6 @@ Examples:
|
|
|
144
191
|
pi-67 xtalpi trend --json
|
|
145
192
|
pi-67 xtalpi drift --json
|
|
146
193
|
pi-67 xtalpi stress --until-done
|
|
194
|
+
pi-67 xtalpi run
|
|
147
195
|
`);
|
|
148
196
|
}
|
package/src/lib/update-plan.mjs
CHANGED
|
@@ -75,6 +75,7 @@ export async function buildUpdatePlan(ctx, options = {}) {
|
|
|
75
75
|
git,
|
|
76
76
|
benignRuntime,
|
|
77
77
|
managerRegistry,
|
|
78
|
+
remote,
|
|
78
79
|
manifest,
|
|
79
80
|
skills,
|
|
80
81
|
external,
|
|
@@ -167,23 +168,35 @@ export function buildPlanDecisions(context) {
|
|
|
167
168
|
recovery: "commit/stash intentional changes or rerun the script-level updater with an explicit dirty override",
|
|
168
169
|
});
|
|
169
170
|
} else if (context.git.dirty && dirty.preservedRuntime.length > 0) {
|
|
171
|
+
const remoteStatus = classifyIncomingRemoteStatus(context.git, context.remote);
|
|
172
|
+
const preserveInPlace = remoteStatus.upToDate;
|
|
170
173
|
actions.push({
|
|
171
174
|
id: "user-runtime-config",
|
|
172
175
|
kind: "runtime-config",
|
|
173
|
-
operation:
|
|
174
|
-
|
|
176
|
+
operation: preserveInPlace
|
|
177
|
+
? "preserve-in-place-no-backup"
|
|
178
|
+
: "conditional-backup-if-incoming-update-touches-runtime-config",
|
|
179
|
+
writes: preserveInPlace
|
|
180
|
+
? []
|
|
181
|
+
: ["~/.pi/pi67/backups/pre-update-runtime-* only if incoming update touches preserved runtime files"],
|
|
175
182
|
preserves: dirty.preservedRuntime,
|
|
176
183
|
risk: "low",
|
|
177
|
-
reason: benignRuntime
|
|
178
|
-
? `benign runtime marker only: ${benignRuntime.reasons.join("; ")}`
|
|
179
|
-
: "only user-owned runtime config files are dirty; update snapshots and restores them instead of overwriting",
|
|
184
|
+
reason: runtimeConfigActionReason({ benignRuntime, preserveInPlace, remoteStatus }),
|
|
180
185
|
benign: benignRuntime.benign,
|
|
181
186
|
benignReasons: benignRuntime.reasons,
|
|
187
|
+
createsNewBackup: !preserveInPlace,
|
|
188
|
+
backupCondition: preserveInPlace
|
|
189
|
+
? "none: remote already matches the local checkout"
|
|
190
|
+
: "only when fetched incoming changes overlap preserved runtime files",
|
|
182
191
|
});
|
|
183
192
|
warnings.push(
|
|
184
|
-
|
|
185
|
-
? `
|
|
186
|
-
:
|
|
193
|
+
preserveInPlace
|
|
194
|
+
? `dirty user runtime config will stay in place; current remote is already at the local commit: ${dirty.preservedRuntime.join(", ")}`
|
|
195
|
+
: (
|
|
196
|
+
benignRuntime.benign
|
|
197
|
+
? `benign user runtime marker will be preserved; backup is conditional on incoming path overlap: ${dirty.preservedRuntime.join(", ")}`
|
|
198
|
+
: `dirty user runtime config will be preserved; backup is conditional on incoming path overlap: ${dirty.preservedRuntime.join(", ")}`
|
|
199
|
+
),
|
|
187
200
|
);
|
|
188
201
|
}
|
|
189
202
|
if (context.git?.dirty && dirty.untracked.length > 0) {
|
|
@@ -289,6 +302,30 @@ export function buildPlanDecisions(context) {
|
|
|
289
302
|
return { actions, blocked, warnings };
|
|
290
303
|
}
|
|
291
304
|
|
|
305
|
+
function classifyIncomingRemoteStatus(git, remote) {
|
|
306
|
+
if (remote?.skipped) return { upToDate: false, known: false, reason: "remote check skipped" };
|
|
307
|
+
if (!remote?.commit || !git?.commit) return { upToDate: false, known: false, reason: remote?.message || "remote commit unknown" };
|
|
308
|
+
const local = String(git.commit);
|
|
309
|
+
const remoteCommit = String(remote.commit);
|
|
310
|
+
return {
|
|
311
|
+
upToDate: remoteCommit.startsWith(local),
|
|
312
|
+
known: true,
|
|
313
|
+
reason: remoteCommit.startsWith(local)
|
|
314
|
+
? "remote already matches local commit"
|
|
315
|
+
: "remote differs from local commit; updater will inspect changed paths after fetch",
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function runtimeConfigActionReason({ benignRuntime, preserveInPlace, remoteStatus }) {
|
|
320
|
+
const prefix = benignRuntime.benign
|
|
321
|
+
? `benign runtime marker only: ${benignRuntime.reasons.join("; ")}`
|
|
322
|
+
: "only user-owned runtime config files are dirty";
|
|
323
|
+
if (preserveInPlace) {
|
|
324
|
+
return `${prefix}; ${remoteStatus.reason}; no runtime backup is needed`;
|
|
325
|
+
}
|
|
326
|
+
return `${prefix}; updater fetches first and creates a runtime backup only if incoming changes touch these preserved files`;
|
|
327
|
+
}
|
|
328
|
+
|
|
292
329
|
export function classifyGitShort(short) {
|
|
293
330
|
const preserved = new Set(PRESERVED_RUNTIME_FILES);
|
|
294
331
|
const result = {
|