@opengsd/gsd-path 1.2.0 → 1.3.1
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 +32 -0
- package/DOCS.md +10 -3
- package/QUICK.md +2 -5
- package/README.md +6 -6
- package/UPDATE.md +28 -2
- package/package.json +1 -1
- package/scripts/install.mjs +24 -2
- package/scripts/install.py +12 -11
- package/scripts/runtime_store.py +18 -3
- package/scripts/wizard.mjs +8 -2
- package/skills/gsd-path/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-build/scripts/__pycache__/check_handoffs.cpython-312.pyc +0 -0
- package/skills/gsd-path-inspect/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-plan/scripts/__pycache__/_common.cpython-312.pyc +0 -0
- package/skills/gsd-path-ship/scripts/__pycache__/_common.cpython-312.pyc +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -66,3 +66,35 @@ are documented here. Format follows [Keep a Changelog](https://keepachangelog.co
|
|
|
66
66
|
- Restore same-cycle recovery after partial review completion
|
|
67
67
|
- Isolate assignment rejections and align native panel exclusions
|
|
68
68
|
- Fix dispatch isolation, legacy pinning, and independent panel selection
|
|
69
|
+
|
|
70
|
+
## [1.3.0] - 2026-09-21
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
- migrate legacy runtimes during upgrades
|
|
74
|
+
|
|
75
|
+
### Changed
|
|
76
|
+
- Clarify receipt reuse and consolidate release policy guidance
|
|
77
|
+
- Clarify legacy upgrade guidance and verify syntax
|
|
78
|
+
- update release notes for v1.2.0
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
- validate installer changes without live host reruns
|
|
82
|
+
- preserve closed review cycles in host recovery
|
|
83
|
+
- clarify owner gates and accept serial task release evidence
|
|
84
|
+
|
|
85
|
+
### Other
|
|
86
|
+
- Clarify reviewer ownership; live verification remains pending
|
|
87
|
+
- Reject unretired verification worktrees and branches in release evidence
|
|
88
|
+
|
|
89
|
+
## [1.3.1] - 2026-09-22
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
- Clarify legacy runtime backup guidance
|
|
93
|
+
- Consolidate legacy runtime migration guidance
|
|
94
|
+
- prepare 1.3.1 patch candidate
|
|
95
|
+
- Clarify legacy runtime migration documentation
|
|
96
|
+
- update release notes for v1.3.0
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
- describe untracked legacy migration in help
|
|
100
|
+
- preserve older untracked runtimes during migration
|
package/DOCS.md
CHANGED
|
@@ -398,12 +398,19 @@ files. The supplied package must match both the version and digest; custom sourc
|
|
|
398
398
|
builds may share a release version while having different digests. No command
|
|
399
399
|
silently substitutes a newer runtime, and status/guards never install or download.
|
|
400
400
|
|
|
401
|
-
For an old
|
|
401
|
+
For an old `.gsd-path/runtime/`, run `gsd-path --runtime-migrate --project
|
|
402
402
|
/absolute/project --dry-run`, then the same command without `--dry-run` and review
|
|
403
403
|
the Git diff. Migration removes the generated files from the checkout and writes
|
|
404
404
|
the declaration and stable wiring. It does not stage or commit. Resolve local
|
|
405
|
-
runtime
|
|
406
|
-
|
|
405
|
+
edits to tracked runtime files or unknown files first. When untracked managed
|
|
406
|
+
files differ from the supplied package, migration saves those files' original
|
|
407
|
+
bytes under `~/.gsd-path/runtime-migrations/backups/` and prints the backup
|
|
408
|
+
directory for review. Interrupted migration is recovered by the same explicit
|
|
409
|
+
command; its journal lives outside the checkout.
|
|
410
|
+
|
|
411
|
+
To migrate and then update skills and project wiring in one invocation, follow
|
|
412
|
+
[Legacy project runtime](UPDATE.md#legacy-project-runtime) for wizard consent,
|
|
413
|
+
unattended commands, dry-run scope, and recovery after an update failure.
|
|
407
414
|
|
|
408
415
|
An ignore rule alone cannot migrate tracked runtime files. This runtime lifecycle
|
|
409
416
|
covers runtime code and launch wiring; project-local skill copies, retained skill
|
package/QUICK.md
CHANGED
|
@@ -67,11 +67,8 @@ node scripts/install.mjs --all --project "$(pwd)"
|
|
|
67
67
|
Installs the [project contracts](DOCS.md#installing)
|
|
68
68
|
(+ `.claude/CLAUDE.md` if Claude is selected).
|
|
69
69
|
If those managed files already exist, a plain install **refuses and installs
|
|
70
|
-
nothing** —
|
|
71
|
-
|
|
72
|
-
hook wiring while keeping your contracts and selected runtime. Merge contract
|
|
73
|
-
changes by hand
|
|
74
|
-
([UPDATE.md](UPDATE.md)).
|
|
70
|
+
nothing** — use [the update guide](UPDATE.md) for existing installations,
|
|
71
|
+
including [legacy runtime migration during upgrades](UPDATE.md#legacy-project-runtime).
|
|
75
72
|
|
|
76
73
|
**Optional** archive/git guards:
|
|
77
74
|
|
package/README.md
CHANGED
|
@@ -16,14 +16,14 @@ Support means the installer and dispatch contract exist. See the
|
|
|
16
16
|
proof and each host's guard tier.
|
|
17
17
|
|
|
18
18
|
<!-- release-docs -->
|
|
19
|
-
**Latest npm release:** [@opengsd/gsd-path@1.
|
|
19
|
+
**Latest npm release:** [@opengsd/gsd-path@1.3.1](https://www.npmjs.com/package/@opengsd/gsd-path/v/1.3.1) — [release notes](CHANGELOG.md#131---2026-09-22)
|
|
20
20
|
|
|
21
21
|
**Recent highlights**
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
22
|
+
- describe untracked legacy migration in help
|
|
23
|
+
- preserve older untracked runtimes during migration
|
|
24
|
+
- Clarify legacy runtime backup guidance
|
|
25
|
+
- Consolidate legacy runtime migration guidance
|
|
26
|
+
- prepare 1.3.1 patch candidate
|
|
27
27
|
|
|
28
28
|
<!-- /release-docs -->
|
|
29
29
|
|
package/UPDATE.md
CHANGED
|
@@ -98,6 +98,32 @@ node scripts/install.mjs --update --claude --cursor
|
|
|
98
98
|
|
|
99
99
|
`--update` only refreshes hosts that **already have** GSD Path installed.
|
|
100
100
|
|
|
101
|
+
### Legacy project runtime
|
|
102
|
+
|
|
103
|
+
The interactive npm installer detects the old `.gsd-path/runtime/` layout when
|
|
104
|
+
you choose to update project wiring. Choose **Migrate and continue upgrade** to
|
|
105
|
+
migrate first, then update skills and wiring. Cancelling leaves the installation
|
|
106
|
+
unchanged. See [Project runtime versions](DOCS.md#project-runtime-versions)
|
|
107
|
+
for migration safety checks and backups. Migration never stages or commits changes.
|
|
108
|
+
|
|
109
|
+
For unattended upgrades, migration requires explicit consent:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx @opengsd/gsd-path@latest --update --runtime-migrate --project "/absolute/project" --dry-run
|
|
113
|
+
npx @opengsd/gsd-path@latest --update --runtime-migrate --project "/absolute/project"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The combined dry run previews migration only and writes nothing. The real command
|
|
117
|
+
migrates, then validates and applies the update. These are separate operations:
|
|
118
|
+
if the update fails, the completed migration remains as an unstaged Git diff.
|
|
119
|
+
Review it with `git status --short` and `git diff` in the project.
|
|
120
|
+
|
|
121
|
+
Migration is needed once per project. For later updates, omit `--runtime-migrate`.
|
|
122
|
+
For runtime selection after migration, see
|
|
123
|
+
[Project runtime versions](DOCS.md#project-runtime-versions).
|
|
124
|
+
Without migration consent, legacy project updates stop before writing files and
|
|
125
|
+
print the exact migration command.
|
|
126
|
+
|
|
101
127
|
### After updating
|
|
102
128
|
|
|
103
129
|
1. Restart your agent session (hosts reload skills on session start).
|
|
@@ -134,8 +160,8 @@ local edits. Clean source checkouts still refresh before a project update.
|
|
|
134
160
|
Older unstamped installs show **Unknown — version metadata unavailable**.
|
|
135
161
|
This does not establish that their runtime is outdated or broken. **Update**
|
|
136
162
|
uses the guarded migration for legacy runtime directories and an upgrade for
|
|
137
|
-
version-pinned runtimes. Migration leaves a reviewable Git diff
|
|
138
|
-
|
|
163
|
+
version-pinned runtimes. Migration leaves a reviewable Git diff; the dashboard
|
|
164
|
+
shows the failure reason when it stops.
|
|
139
165
|
See [Project runtime versions](DOCS.md#project-runtime-versions) for manual
|
|
140
166
|
migration and preview commands. Use `--doctor --project PATH` to check runtime files.
|
|
141
167
|
|
package/package.json
CHANGED
package/scripts/install.mjs
CHANGED
|
@@ -1655,7 +1655,7 @@ function usage() {
|
|
|
1655
1655
|
" --hooks-init add guards to an existing project without changing its contracts\n" +
|
|
1656
1656
|
" --runtime-restore restore the exact declared runtime from --source-root\n" +
|
|
1657
1657
|
" --runtime-upgrade explicitly select the supplied package runtime\n" +
|
|
1658
|
-
" --runtime-migrate migrate a
|
|
1658
|
+
" --runtime-migrate migrate a legacy runtime for review; add --update to continue upgrading\n" +
|
|
1659
1659
|
" --hooks-refresh validate the selected runtime; keep its version\n" +
|
|
1660
1660
|
" --hooks-refresh-full refresh native settings/git hooks; target flags create missing configs\n" +
|
|
1661
1661
|
" --dry-run preview without writing\n" +
|
|
@@ -1684,6 +1684,8 @@ export async function main(argv, env = process.env) {
|
|
|
1684
1684
|
colored: !process.env.NO_COLOR,
|
|
1685
1685
|
version: packageVersion(),
|
|
1686
1686
|
targets: TARGETS,
|
|
1687
|
+
legacyRuntime: lexists(path.join(process.cwd(), ".gsd-path", "runtime")) &&
|
|
1688
|
+
!lexists(path.join(process.cwd(), ".gsd-path", "runtime.json")),
|
|
1687
1689
|
installed: (target, local) =>
|
|
1688
1690
|
hasManagedInstall(local ? localRoot(target, process.cwd()) : defaultRoot(target, env)),
|
|
1689
1691
|
});
|
|
@@ -1695,7 +1697,12 @@ export async function main(argv, env = process.env) {
|
|
|
1695
1697
|
: path.resolve(SCRIPT_DIRECTORY, "..");
|
|
1696
1698
|
const project =
|
|
1697
1699
|
values.project !== undefined ? absolutePath(values.project) : null;
|
|
1698
|
-
|
|
1700
|
+
const migrateAndUpdate = values["runtime-migrate"] && values.update;
|
|
1701
|
+
if (migrateAndUpdate && (project === null || values["runtime-restore"] || values["runtime-upgrade"] || values.doctor || values["hooks-init"] || values["hooks-refresh"] || values["hooks-refresh-full"])) {
|
|
1702
|
+
ui.error("--runtime-migrate --update requires --project and cannot be combined with another runtime, doctor, or hook operation");
|
|
1703
|
+
return 2;
|
|
1704
|
+
}
|
|
1705
|
+
if (!migrateAndUpdate && ["runtime-restore", "runtime-upgrade", "runtime-migrate"].some(name => values[name])) {
|
|
1699
1706
|
const interpreter = requiredPythonRuntime("project runtime");
|
|
1700
1707
|
const result = spawnSync(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"),
|
|
1701
1708
|
...argv.filter(arg => arg !== "--no-color")], { stdio: "inherit", env });
|
|
@@ -1800,6 +1807,21 @@ export async function main(argv, env = process.env) {
|
|
|
1800
1807
|
} else {
|
|
1801
1808
|
plans = selected.map((target) => targetPlan(target, rootFor(target)));
|
|
1802
1809
|
}
|
|
1810
|
+
if (migrateAndUpdate) {
|
|
1811
|
+
const interpreter = requiredPythonRuntime("project runtime");
|
|
1812
|
+
const migration = spawnSync(interpreter, ["-B", path.join(SCRIPT_DIRECTORY, "install.py"),
|
|
1813
|
+
"--runtime-migrate", "--project", project, "--source-root", sourceRoot,
|
|
1814
|
+
...(values["dry-run"] ? ["--dry-run"] : [])], { stdio: "inherit", env });
|
|
1815
|
+
if (migration.error || migration.status !== 0) {
|
|
1816
|
+
if (migration.error) ui.error(migration.error.message);
|
|
1817
|
+
return migration.status ?? 1;
|
|
1818
|
+
}
|
|
1819
|
+
if (values["dry-run"]) {
|
|
1820
|
+
ui.result("Migration preview only; nothing was written. Run without --dry-run to migrate, then validate and apply the update.");
|
|
1821
|
+
return 0;
|
|
1822
|
+
}
|
|
1823
|
+
ui.result("Migration completed. Review the unstaged Git diff. Updating skills and wiring next; an update failure will retain the completed migration.");
|
|
1824
|
+
}
|
|
1803
1825
|
const spin = ui.spinner("Preparing");
|
|
1804
1826
|
try {
|
|
1805
1827
|
const results = await install(sourceRootForInstall, plans, {
|
package/scripts/install.py
CHANGED
|
@@ -2641,17 +2641,16 @@ def install(
|
|
|
2641
2641
|
|
|
2642
2642
|
if project is not None:
|
|
2643
2643
|
_validate_directory_destination(project, "project path")
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
)
|
|
2644
|
+
_validate_project(
|
|
2645
|
+
source_root,
|
|
2646
|
+
project,
|
|
2647
|
+
selected,
|
|
2648
|
+
hooks,
|
|
2649
|
+
[*mutation_roots, *planned_backups],
|
|
2650
|
+
interpreter,
|
|
2651
|
+
hooks_dir,
|
|
2652
|
+
update,
|
|
2653
|
+
)
|
|
2655
2654
|
|
|
2656
2655
|
results = []
|
|
2657
2656
|
with tempfile.TemporaryDirectory(prefix="gsd-path-install-") as temporary:
|
|
@@ -2840,6 +2839,8 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
|
2840
2839
|
_release_install_locks(locks, created)
|
|
2841
2840
|
print(f"runtime: {runtime_action} {pin['version']} ({pin['digest']})"
|
|
2842
2841
|
+ (" (dry run)" if arguments.dry_run else ""))
|
|
2842
|
+
if pin.get("backup"):
|
|
2843
|
+
print(f"legacy runtime backup: {pin['backup']}")
|
|
2843
2844
|
return 0
|
|
2844
2845
|
except (InstallerError, OSError, ValueError) as error:
|
|
2845
2846
|
print(f"error: {error}", file=sys.stderr)
|
package/scripts/runtime_store.py
CHANGED
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
import os
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
import shutil
|
|
9
|
+
import shlex
|
|
9
10
|
import tempfile
|
|
10
11
|
from contextlib import contextmanager
|
|
11
12
|
|
|
@@ -135,7 +136,9 @@ def prepare(source, project, *, dry_run=False):
|
|
|
135
136
|
status_runtime.validate_runtime(pin)
|
|
136
137
|
return pin
|
|
137
138
|
if os.path.lexists(project / ".gsd-path/runtime"):
|
|
138
|
-
raise ValueError("legacy project runtime requires
|
|
139
|
+
raise ValueError("legacy project runtime requires explicit migration before updating; run: "
|
|
140
|
+
f"npx @opengsd/gsd-path@latest --runtime-migrate --project {shlex.quote(str(project.resolve()))}; "
|
|
141
|
+
"then retry the update. Migration produces a reviewable Git diff")
|
|
139
142
|
return publish(source, dry_run=dry_run)
|
|
140
143
|
|
|
141
144
|
|
|
@@ -232,6 +235,7 @@ def operate(source, project, action, *, dry_run=False):
|
|
|
232
235
|
raise ValueError("legacy runtime contains unknown files; preserve and resolve them before migration")
|
|
233
236
|
managed = entries + [project / ".gsd-path/status_runtime.py"]
|
|
234
237
|
managed += [project / ".gsd-path" / n for n in GUARDS if (project / ".gsd-path" / n).exists()]
|
|
238
|
+
divergent = []
|
|
235
239
|
for path in managed:
|
|
236
240
|
if path.is_symlink() or not path.is_file():
|
|
237
241
|
raise ValueError(f"unsafe migration file: {path}")
|
|
@@ -246,7 +250,7 @@ def operate(source, project, action, *, dry_run=False):
|
|
|
246
250
|
if clean.stdout:
|
|
247
251
|
raise ValueError(f"locally modified migration file: {path}")
|
|
248
252
|
elif path.read_bytes() != (source / "scripts" / path.name).read_bytes():
|
|
249
|
-
|
|
253
|
+
divergent.append(path)
|
|
250
254
|
pin = publish(source, dry_run=dry_run)
|
|
251
255
|
if dry_run:
|
|
252
256
|
return pin
|
|
@@ -254,6 +258,17 @@ def operate(source, project, action, *, dry_run=False):
|
|
|
254
258
|
declaration = project / ".gsd-path/runtime.json"
|
|
255
259
|
if os.path.lexists(declaration):
|
|
256
260
|
raise ValueError("runtime declaration already exists; refusing legacy migration")
|
|
261
|
+
backup = None
|
|
262
|
+
if divergent:
|
|
263
|
+
backup_root = migration_journal(project).parent / "backups"
|
|
264
|
+
if backup_root.is_symlink():
|
|
265
|
+
raise ValueError(f"unsafe runtime migration backup directory: {backup_root}")
|
|
266
|
+
backup_root.mkdir(parents=True, exist_ok=True)
|
|
267
|
+
backup = Path(tempfile.mkdtemp(prefix="legacy-", dir=backup_root))
|
|
268
|
+
for path in divergent:
|
|
269
|
+
saved = backup / path.relative_to(project / ".gsd-path")
|
|
270
|
+
saved.parent.mkdir(parents=True, exist_ok=True)
|
|
271
|
+
saved.write_bytes(originals[path])
|
|
257
272
|
updates = {project / ".gsd-path/status_runtime.py": (source / "scripts/status_runtime.py").read_bytes(),
|
|
258
273
|
declaration: pin_text(pin).encode()}
|
|
259
274
|
updates.update({p: None for p in entries})
|
|
@@ -275,4 +290,4 @@ def operate(source, project, action, *, dry_run=False):
|
|
|
275
290
|
recover_migration(project)
|
|
276
291
|
raise
|
|
277
292
|
journal.unlink()
|
|
278
|
-
return pin
|
|
293
|
+
return {**pin, "backup": str(backup)} if backup else pin
|
package/scripts/wizard.mjs
CHANGED
|
@@ -125,7 +125,7 @@ const confirm = (io, theme, title, yes = "Yes", no = "No") =>
|
|
|
125
125
|
|
|
126
126
|
// Pure: runs the question flow and returns install.mjs argv (or null if cancelled).
|
|
127
127
|
// `installed(target, local)` reports whether a managed install already exists for that host in that scope.
|
|
128
|
-
export async function wizard({ input, output, colored = true, version = "", cwd = process.cwd(), installed = () => false, targets }) {
|
|
128
|
+
export async function wizard({ input, output, colored = true, version = "", cwd = process.cwd(), installed = () => false, legacyRuntime = false, targets }) {
|
|
129
129
|
const theme = makeTheme(colored);
|
|
130
130
|
const keys = keyReader(input);
|
|
131
131
|
const io = { keys, output };
|
|
@@ -147,8 +147,13 @@ export async function wizard({ input, output, colored = true, version = "", cwd
|
|
|
147
147
|
hostItems.some((item) => item.checked && hosts.includes(item.value)) &&
|
|
148
148
|
(await confirm(io, theme, "Existing installs found. What do you want to do?", "Update in place", "Fresh install"));
|
|
149
149
|
|
|
150
|
-
const project = await confirm(io, theme, "Write AGENTS.md + WORKFLOW.md contracts into this repo?", `Yes — ${cwd}`, "Not now");
|
|
150
|
+
const project = await confirm(io, theme, update ? "Refresh this project's wiring (keep its contracts and selected runtime)?" : "Write AGENTS.md + WORKFLOW.md contracts into this repo?", `Yes — ${cwd}`, "Not now");
|
|
151
151
|
const hooks = project && (await confirm(io, theme, "Install guard hooks (archive immutability, ship-commit purity)?"));
|
|
152
|
+
const migrate = project && update && legacyRuntime;
|
|
153
|
+
if (migrate) {
|
|
154
|
+
output.write(`\n This project uses the old runtime layout. Migration moves managed runtime files\n outside the repo and leaves an unstaged Git diff for review. Local edits are preserved.\n`);
|
|
155
|
+
if (!(await confirm(io, theme, "Migrate this project before updating?", "Migrate and continue upgrade", "Cancel"))) return null;
|
|
156
|
+
}
|
|
152
157
|
|
|
153
158
|
const argv = [];
|
|
154
159
|
if (hosts.length === targets.length) argv.push("--all");
|
|
@@ -157,6 +162,7 @@ export async function wizard({ input, output, colored = true, version = "", cwd
|
|
|
157
162
|
if (local) argv.push("--local");
|
|
158
163
|
if (project) argv.push("--project", cwd);
|
|
159
164
|
if (hooks) argv.push("--hooks");
|
|
165
|
+
if (migrate) argv.push("--runtime-migrate");
|
|
160
166
|
|
|
161
167
|
output.write(`\n ${theme.dim("Equivalent command:")}\n ${theme.accent("$")} gsd-path ${argv.join(" ")}\n`);
|
|
162
168
|
const go = await select(io, theme, "Ready?", [
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|