@opengsd/gsd-path 1.2.0 → 1.3.0

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
@@ -66,3 +66,22 @@ 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
package/DOCS.md CHANGED
@@ -405,6 +405,10 @@ the declaration and stable wiring. It does not stage or commit. Resolve local
405
405
  runtime edits or unknown files first. Interrupted migration is recovered by the
406
406
  same explicit command; its journal lives outside the checkout.
407
407
 
408
+ To migrate and then update skills and project wiring in one invocation, follow
409
+ [Legacy project runtime](UPDATE.md#legacy-project-runtime) for wizard consent,
410
+ unattended commands, dry-run scope, and recovery after an update failure.
411
+
408
412
  An ignore rule alone cannot migrate tracked runtime files. This runtime lifecycle
409
413
  covers runtime code and launch wiring; project-local skill copies, retained skill
410
414
  backups, and mixed user/host settings remain separately owned installation output.
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** — see [project runtime versions](DOCS.md#project-runtime-versions)
71
- for legacy migration, then use `--update --project PATH` to refresh skills and
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.2.0](https://www.npmjs.com/package/@opengsd/gsd-path/v/1.2.0) — [release notes](CHANGELOG.md#120---2026-09-20)
19
+ **Latest npm release:** [@opengsd/gsd-path@1.3.0](https://www.npmjs.com/package/@opengsd/gsd-path/v/1.3.0) — [release notes](CHANGELOG.md#130---2026-09-21)
20
20
 
21
21
  **Recent highlights**
22
- - add Path settings and project history dashboard
23
- - add optional Jev evidence screening
24
- - store worktrees and pinned runtimes outside project checkouts
25
- - centralize sub-agent model selection policy
26
- - reuse unchanged host receipts for releases
22
+ - migrate legacy runtimes during upgrades
23
+ - validate installer changes without live host reruns
24
+ - preserve closed review cycles in host recovery
25
+ - clarify owner gates and accept serial task release evidence
26
+ - Clarify receipt reuse and consolidate release policy guidance
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. Migration preserves locally modified files by stopping for you to
107
+ resolve them; it 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).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengsd/gsd-path",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Disk-backed AI agent pipeline. Docs: DOCS.md (start here), QUICK.md, FULL.md, UPDATE.md",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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 tracked legacy runtime for review\n" +
1658
+ " --runtime-migrate migrate a tracked 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
- if (["runtime-restore", "runtime-upgrade", "runtime-migrate"].some(name => values[name])) {
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, {
@@ -2641,17 +2641,16 @@ def install(
2641
2641
 
2642
2642
  if project is not None:
2643
2643
  _validate_directory_destination(project, "project path")
2644
- if dry_run:
2645
- _validate_project(
2646
- source_root,
2647
- project,
2648
- selected,
2649
- hooks,
2650
- [*mutation_roots, *planned_backups],
2651
- interpreter,
2652
- hooks_dir,
2653
- update,
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:
@@ -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 --runtime-migrate --project PATH; migration produces a reviewable Git diff")
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
 
@@ -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?", [