gentle-pi 1.2.0 → 2.0.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.
Files changed (107) hide show
  1. package/README.md +40 -9
  2. package/assets/agents/review-readability.md +0 -1
  3. package/assets/agents/review-reliability.md +0 -1
  4. package/assets/agents/review-resilience.md +0 -1
  5. package/assets/agents/review-risk.md +0 -1
  6. package/contracts/review-integration/v1/fixtures/binding-revision-conflict.fixture.json +25 -0
  7. package/contracts/review-integration/v1/fixtures/capabilities-v1.2.fixture.json +263 -0
  8. package/contracts/review-integration/v1/fixtures/capabilities-v1.3.fixture.json +301 -0
  9. package/contracts/review-integration/v1/fixtures/capabilities-v1.4.fixture.json +312 -0
  10. package/contracts/review-integration/v1/fixtures/capabilities-v1.5.fixture.json +321 -0
  11. package/contracts/review-integration/v1/fixtures/consent.fixture.json +36 -0
  12. package/contracts/review-integration/v1/fixtures/final-verification-incident.fixture.json +1 -0
  13. package/contracts/review-integration/v1/fixtures/operation.fixture.json +4 -0
  14. package/contracts/review-integration/v1/fixtures/repair-preflight.fixture.json +54 -0
  15. package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +97 -0
  16. package/contracts/review-integration/v1/fixtures/start.fixture.json +25 -1
  17. package/contracts/review-integration/v1/fixtures/status-ambiguous.fixture.json +4 -0
  18. package/contracts/review-integration/v1/fixtures/status-corrupted.fixture.json +4 -0
  19. package/contracts/review-integration/v1/fixtures/status-recover.fixture.json +4 -0
  20. package/contracts/review-integration/v1/fixtures/status-unrelated.fixture.json +4 -0
  21. package/contracts/review-integration/v1/fixtures/status-v2-ambiguous.fixture.json +110 -0
  22. package/contracts/review-integration/v1/fixtures/status-v2-corrupted.fixture.json +107 -0
  23. package/contracts/review-integration/v1/fixtures/status-v2-final-verification-retry.fixture.json +192 -0
  24. package/contracts/review-integration/v1/fixtures/status-v2-recover.fixture.json +130 -0
  25. package/contracts/review-integration/v1/fixtures/status-v2-repair.fixture.json +161 -0
  26. package/contracts/review-integration/v1/fixtures/status-v2-unrelated.fixture.json +103 -0
  27. package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +193 -0
  28. package/contracts/review-integration/v1/fixtures/status.fixture.json +19 -5
  29. package/contracts/review-integration/v1/fixtures/verification-evidence.fixture.json +1 -0
  30. package/contracts/review-integration/v1/schemas/admitted-result.schema.json +35 -0
  31. package/contracts/review-integration/v1/schemas/artifact-subject.schema.json +33 -0
  32. package/contracts/review-integration/v1/schemas/authority-repair-assessment.schema.json +247 -0
  33. package/contracts/review-integration/v1/schemas/capabilities-v1.2.schema.json +249 -0
  34. package/contracts/review-integration/v1/schemas/capabilities-v1.3.schema.json +429 -0
  35. package/contracts/review-integration/v1/schemas/capabilities-v1.4.schema.json +432 -0
  36. package/contracts/review-integration/v1/schemas/capabilities-v1.5.schema.json +154 -0
  37. package/contracts/review-integration/v1/schemas/consent.schema.json +86 -0
  38. package/contracts/review-integration/v1/schemas/failure.schema.json +54 -8
  39. package/contracts/review-integration/v1/schemas/final-verification-incident.schema.json +51 -0
  40. package/contracts/review-integration/v1/schemas/operation.schema.json +204 -31
  41. package/contracts/review-integration/v1/schemas/repair.schema.json +251 -0
  42. package/contracts/review-integration/v1/schemas/result-artifact-v2.schema.json +38 -0
  43. package/contracts/review-integration/v1/schemas/start-v2.schema.json +216 -0
  44. package/contracts/review-integration/v1/schemas/start.schema.json +79 -3
  45. package/contracts/review-integration/v1/schemas/status-v2.schema.json +313 -0
  46. package/contracts/review-integration/v1/schemas/status.schema.json +37 -10
  47. package/contracts/review-integration/v1/schemas/targeted-validation-request.schema.json +46 -0
  48. package/contracts/review-integration/v1/schemas/verification-evidence.schema.json +64 -0
  49. package/contracts/review-integration/v2/fixtures/capabilities.fixture.json +111 -0
  50. package/contracts/review-integration/v2/fixtures/consent.fixture.json +36 -0
  51. package/contracts/review-integration/v2/fixtures/start.fixture.json +93 -0
  52. package/contracts/review-integration/v2/fixtures/status.fixture.json +113 -0
  53. package/contracts/review-integration/v2/schemas/admitted-result.schema.json +14 -0
  54. package/contracts/review-integration/v2/schemas/artifact-subject.schema.json +36 -0
  55. package/contracts/review-integration/v2/schemas/capabilities.schema.json +111 -0
  56. package/contracts/review-integration/v2/schemas/consent.schema.json +70 -0
  57. package/contracts/review-integration/v2/schemas/failure.schema.json +35 -0
  58. package/contracts/review-integration/v2/schemas/operation.schema.json +46 -0
  59. package/contracts/review-integration/v2/schemas/repair.schema.json +22 -0
  60. package/contracts/review-integration/v2/schemas/start.schema.json +106 -0
  61. package/contracts/review-integration/v2/schemas/status.schema.json +104 -0
  62. package/docs/native-authority-architecture.md +18 -3
  63. package/docs/review-integration.md +175 -27
  64. package/extensions/gentle-ai.ts +414 -19
  65. package/lib/gentle-ai-binary.ts +1 -1
  66. package/lib/native-review-cli.ts +679 -57
  67. package/lib/review-candidate-view.ts +131 -1
  68. package/lib/review-compact-contract.ts +12 -3
  69. package/lib/review-consent-latch.ts +51 -0
  70. package/lib/review-correction-lifecycle.ts +170 -0
  71. package/lib/review-integration-v2.ts +1563 -0
  72. package/package.json +15 -11
  73. package/runtime/gentle-ai-binary.mjs +1 -1
  74. package/runtime/native-review-cli.mjs +663 -41
  75. package/runtime/review-integration-v2.mjs +1564 -0
  76. package/scripts/build-git-commit-transaction-runner.mjs +1 -1
  77. package/scripts/gentle-ai-installer.mjs +16 -11
  78. package/scripts/install-gentle-ai.mjs +7 -3
  79. package/scripts/test-packed-runner.mjs +2 -2
  80. package/scripts/verify-package-files.mjs +216 -64
  81. package/skills/release/SKILL.md +38 -11
  82. package/tests/devbinary/native-review-parity.devtest.ts +348 -0
  83. package/tests/gentle-ai-binary.test.ts +8 -1
  84. package/tests/gentle-ai-installer.test.ts +24 -21
  85. package/tests/native-binary-gate.test.ts +145 -0
  86. package/tests/native-review-capability-contract.test.ts +87 -0
  87. package/tests/native-review-cli.test.ts +44 -1
  88. package/tests/native-review-parity-runtime.test.ts +70 -23
  89. package/tests/native-review-parity.test.ts +673 -0
  90. package/tests/native-risk-evidence-parity.test.ts +91 -0
  91. package/tests/package-manifest.test.ts +72 -7
  92. package/tests/review-actor-tool-deny.test.ts +20 -5
  93. package/tests/review-candidate-view.test.ts +331 -1
  94. package/tests/review-compact-contract.test.ts +5 -0
  95. package/tests/review-consent-latch.test.ts +67 -0
  96. package/tests/review-controller-native-recovery.test.ts +180 -5
  97. package/tests/review-controller-native-routing.test.ts +385 -12
  98. package/tests/review-controller-workspace-root.test.ts +26 -6
  99. package/tests/review-correction-lifecycle.test.ts +174 -0
  100. package/tests/review-integration-v2.test.ts +539 -0
  101. package/tests/runtime-harness.mjs +187 -0
  102. package/tests/support/native-binary-gate.ts +86 -0
  103. package/tests/verify-package-files.test.ts +119 -0
  104. package/lib/review-integration-v1.ts +0 -795
  105. package/runtime/review-integration-v1.mjs +0 -796
  106. package/tests/native-review-integration-v1.test.ts +0 -102
  107. package/tests/review-integration-v1.test.ts +0 -530
@@ -8,7 +8,7 @@ import { fileURLToPath } from "node:url";
8
8
  const root = fileURLToPath(new URL("..", import.meta.url));
9
9
  const sources = [
10
10
  "gentle-ai-binary",
11
- "review-integration-v1",
11
+ "review-integration-v2",
12
12
  "native-review-cli",
13
13
  "git-commit-transaction",
14
14
  ];
@@ -19,31 +19,36 @@ import { execFile } from "node:child_process";
19
19
  import { promisify } from "node:util";
20
20
 
21
21
  const execFileAsync = promisify(execFile);
22
- const RELEASE_BASE_URL = "https://github.com/Gentleman-Programming/gentle-ai/releases/download/v2.1.11/";
22
+ const RELEASE_BASE_URL = "https://github.com/Gentleman-Programming/gentle-ai/releases/download/v2.2.2/";
23
23
  const MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
24
24
  const MAX_REDIRECTS = 3;
25
25
  const DOWNLOAD_TIMEOUTS = { headers: 10_000, body: 30_000, attempts: 2, retryDelay: 100 };
26
- const INSTALLER_VERSION = "2.1.11";
26
+ export const INSTALLER_VERSION = "2.2.2";
27
27
 
28
28
  // Sentinel used while a re-pinned gentle-ai release is not yet published. A
29
29
  // sentinel digest can never match a real SHA-256, so installation fails closed,
30
30
  // and verify-package-files.mjs refuses to pack/publish while any digest below
31
- // still holds it. The v2.1.11 digests are pinned from the published release:
32
- // archive sha256 values verified against checksums.txt and freshly computed
33
- // hashes; binary sha256 values computed from the extracted executables.
31
+ // still holds it. The v2.2.2 digests are pinned from the published release:
32
+ // archive sha256 values verified against the minisign-signed checksums.txt and
33
+ // freshly computed hashes; binary sha256 values computed from the extracted
34
+ // executables.
34
35
  export const GENTLE_AI_PENDING_DIGEST = "PENDING-GENTLE-AI-RELEASE-DIGEST";
35
36
 
36
37
  function asset(name, sha256, binarySha256, executable) {
37
38
  return Object.freeze({ name, sha256, binarySha256, executable, url: `${RELEASE_BASE_URL}${name}` });
38
39
  }
39
40
 
41
+ // Windows is absent on purpose. gentle-ai stopped distributing Windows builds
42
+ // in c4b764d0 ("omit unsigned Windows distribution"), so v2.2.2 publishes only
43
+ // darwin and linux archives and there is nothing to pin. A Windows caller now
44
+ // gets resolveGentleAiReleaseAsset's unsupported-platform error, which is the
45
+ // truth: Pi cannot install a binary that upstream does not publish. Restore
46
+ // both rows the moment gentle-ai ships signed Windows assets again.
40
47
  export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
41
- "darwin/amd64": asset("gentle-ai_2.1.11_darwin_amd64.tar.gz", "80c2a6bba981fd0ec1fdc9b21d9b80c03853cacc644b307ca7baf1faa6b376d2", "bffca50cb51b07cc1c428e21616ea1cf7d25970643fed13ab0aa10fd359bbd02", "gentle-ai"),
42
- "darwin/arm64": asset("gentle-ai_2.1.11_darwin_arm64.tar.gz", "27e49a5104ff1ab156276e191b845f266135afe9339f111689ddf340b562c926", "4e39e520abf116a53ff5e36a0cd65756b6fadfa528489f21e9c722bfa00af620", "gentle-ai"),
43
- "linux/amd64": asset("gentle-ai_2.1.11_linux_amd64.tar.gz", "d115aaf5724a71503150ebf740769e7aa52e41e673ac39bf5b0ff1be4e3324b0", "45788ac6cf37a6165f50aca3503d4af4e7e375e518fc67010064dccf78f98d2d", "gentle-ai"),
44
- "linux/arm64": asset("gentle-ai_2.1.11_linux_arm64.tar.gz", "aea3a4b0064b57df5f831fdf6b27b29e9898a8f9393fd648dea4a8ba563e477e", "2bf929993ba03dd02401a3c35d8b0bff7801717e43d17b39e29f3cd389894e63", "gentle-ai"),
45
- "windows/amd64": asset("gentle-ai_2.1.11_windows_amd64.zip", "43fb4e66cf36a7f01d2d343f59fe8ff37396fc0e98fb24abcd95bc79c2450e9a", "1c92cdcaf826d4bec6a6560770e5a0edefc09997364a664a5033abc6ab932b2b", "gentle-ai.exe"),
46
- "windows/arm64": asset("gentle-ai_2.1.11_windows_arm64.zip", "9c2884075161715f0efac1ce55cd08f2d9313068f125443ade8de3a83645258a", "a903bface64af274a28cfdd08b5c55c67fc554bac9279d7d466fc3f99020bd89", "gentle-ai.exe"),
48
+ "darwin/amd64": asset("gentle-ai_2.2.2_darwin_amd64.tar.gz", "5ca67829903bf4c6b14665664f80f9d8216c84b10c8e50870d297f452cefb9dc", "9b239423450562d026384f482bbd2f1e3f2820431a84f0921743ac3df9d632de", "gentle-ai"),
49
+ "darwin/arm64": asset("gentle-ai_2.2.2_darwin_arm64.tar.gz", "0193e1a284444dccee2863d31b8dbb76a982e8f9111955908d6a9131c1a5490e", "149b97248552c5e03ebc4d991f86b1360fb847a40fc315555a8aa256f95baca0", "gentle-ai"),
50
+ "linux/amd64": asset("gentle-ai_2.2.2_linux_amd64.tar.gz", "b85bbb20eb2236de97b261df16cfc8d8394dfd07a137e885c4889b62d0c20fa1", "00d5732e8dd3945956800217a4f60213c2d9ca63351092a2cb7f4e5f9ece54f9", "gentle-ai"),
51
+ "linux/arm64": asset("gentle-ai_2.2.2_linux_arm64.tar.gz", "61e7077342448273f0c43af49ce4d182594bab5e4f86f812975af2fbe69e3b0b", "a92685aa7dbea0cc4297d016569b3defe9bb30a7374a620021e245b74f50eb68", "gentle-ai"),
47
52
  });
48
53
 
49
54
  function upstreamArchitecture(architecture) {
@@ -1,14 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { installGentleAi } from "./gentle-ai-installer.mjs";
2
+ // The version is read from the installer rather than written here. Two
3
+ // hardcoded copies of it survived a pin bump once and reported installing
4
+ // v2.1.11 while writing v2.2.0 to disk, which is the one moment an operator
5
+ // most needs the number to be true.
6
+ import { INSTALLER_VERSION, installGentleAi } from "./gentle-ai-installer.mjs";
3
7
 
4
8
  if (process.env.GENTLE_PI_SKIP_GENTLE_AI_INSTALL === "1") {
5
9
  console.warn("GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1: skipped package-local Gentle AI installation; native review operations will fail with package-local-binary-missing until gentle-pi is reinstalled.");
6
10
  } else {
7
11
  try {
8
12
  const result = await installGentleAi();
9
- console.log(`Gentle AI v2.1.11 ${result.installed ? "installed" : "integrity-verified"} at ${result.binaryPath}`);
13
+ console.log(`Gentle AI v${INSTALLER_VERSION} ${result.installed ? "installed" : "integrity-verified"} at ${result.binaryPath}`);
10
14
  } catch (error) {
11
- console.error(`gentle-pi could not install its package-local Gentle AI v2.1.11 binary: ${error instanceof Error ? error.message : String(error)}`);
15
+ console.error(`gentle-pi could not install its package-local Gentle AI v${INSTALLER_VERSION} binary: ${error instanceof Error ? error.message : String(error)}`);
12
16
  process.exitCode = 1;
13
17
  }
14
18
  }
@@ -57,8 +57,8 @@ try {
57
57
  const versions = readdirSync(join(packageRoot, ".gentle-ai"), { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^v\d+\.\d+\.\d+$/.test(entry.name));
58
58
  if (versions.length !== 1) throw new Error("packed install did not contain exactly one package-local Gentle AI version");
59
59
  const executable = join(packageRoot, ".gentle-ai", versions[0].name, process.platform === "win32" ? "gentle-ai.exe" : "gentle-ai");
60
- const capabilities = JSON.parse(execFileSync(executable, ["review", "capabilities", "--contract", "gentle-ai.review-integration/v1"], { cwd: installDirectory, encoding: "utf8" }));
61
- if (!["gentle-ai.review-integration.capabilities/v1", "gentle-ai.review-integration.capabilities/v1.1"].includes(capabilities.schema) || capabilities.contract !== "gentle-ai.review-integration/v1" || capabilities.package?.version !== versions[0].name.slice(1)) throw new Error("package-local Gentle AI returned incompatible capabilities");
60
+ const capabilities = JSON.parse(execFileSync(executable, ["review", "capabilities", "--contract", "gentle-ai.review-integration/v2"], { cwd: installDirectory, encoding: "utf8" }));
61
+ if (!["gentle-ai.review-integration.capabilities/v2"].includes(capabilities.schema) || capabilities.contract !== "gentle-ai.review-integration/v2" || capabilities.package?.version !== versions[0].name.slice(1)) throw new Error("package-local Gentle AI returned incompatible capabilities");
62
62
  const packageManifest = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
63
63
  process.stdout.write(`packed runner E2E passed (gentle-pi ${packageManifest.version ?? "unknown"}; Gentle AI ${capabilities.package?.version ?? "unknown"}; ${result.states.length} states)\n`);
64
64
  } finally {
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { createHash } from "node:crypto";
4
- import { existsSync, readFileSync, statSync } from "node:fs";
5
- import { join } from "node:path";
6
- import { fileURLToPath } from "node:url";
4
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
5
+ import { join, relative, sep } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
7
 
8
8
  const root = join(fileURLToPath(new URL("..", import.meta.url)));
9
9
 
@@ -43,12 +43,12 @@ const requiredPaths = [
43
43
  "lib/gentle-ai-binary.ts",
44
44
  "lib/git-commit-transaction.ts",
45
45
  "lib/native-review-cli.ts",
46
- "lib/review-integration-v1.ts",
46
+ "lib/review-integration-v2.ts",
47
47
  "lib/sdd-preflight.ts",
48
48
  "runtime/gentle-ai-binary.mjs",
49
49
  "runtime/git-commit-transaction.mjs",
50
50
  "runtime/native-review-cli.mjs",
51
- "runtime/review-integration-v1.mjs",
51
+ "runtime/review-integration-v2.mjs",
52
52
  "scripts/build-git-commit-transaction-runner.mjs",
53
53
  "scripts/gentle-ai-installer.mjs",
54
54
  "scripts/install-gentle-ai.mjs",
@@ -76,84 +76,236 @@ const requiredPaths = [
76
76
  ];
77
77
 
78
78
  const contractHashes = {
79
+ "contracts/review-integration/v1/fixtures/binding-revision-conflict.fixture.json": "c2e294843cee5185324cb7a41702574ef94852517239d99e7493a1414a60b363",
79
80
  "contracts/review-integration/v1/fixtures/capabilities-v1.1.fixture.json": "1b3dc40dce7bfb5d3ecc7e92af68d66e71b733ba0b0f71ba94d3c633adc48bcf",
81
+ "contracts/review-integration/v1/fixtures/capabilities-v1.2.fixture.json": "2970d21cd95a7fcaea6547c47a591a5151046e7ede658b3e8c5b9a9c5d106b65",
82
+ "contracts/review-integration/v1/fixtures/capabilities-v1.3.fixture.json": "0ec783ea13b4c82c0b002c5caa758f33e2b488537297cc2d0694ec92176ac0cb",
83
+ "contracts/review-integration/v1/fixtures/capabilities-v1.4.fixture.json": "84e0db457b76b97b35c2be772dfc647f9eab66810ea98f64fed85645c3c266ba",
84
+ "contracts/review-integration/v1/fixtures/capabilities-v1.5.fixture.json": "0cc952af3767c393bde9e4785e4071615a529fd672bc94da4fcc204780524a27",
80
85
  "contracts/review-integration/v1/fixtures/capabilities.fixture.json": "b3ca822189a236f2d891628c665ca23e308bf5185a1701e1f07231bd970461bb",
86
+ "contracts/review-integration/v1/fixtures/consent.fixture.json": "b2ff4809b9eb75a54206800dca13f96396976aca7387e45f765ac6ec98029cd1",
81
87
  "contracts/review-integration/v1/fixtures/failure.fixture.json": "e72b6ab5e3c529abac47bd324444f84ca90f67ef0a67189f5fd8d24d199a2759",
82
- "contracts/review-integration/v1/fixtures/operation.fixture.json": "f867bead654d467319d85ff518b39f145996df6a9494fdbff798516928dacfcd",
83
- "contracts/review-integration/v1/fixtures/start.fixture.json": "d61267ee2e0a5e4c64cf48f40b4882d2284bfdaad9942a09ee29d7e693012238",
84
- "contracts/review-integration/v1/fixtures/status-ambiguous.fixture.json": "0f2737c2ed67bb2edb184faa8ac7006ef4f9172b9929aaa063432871c2aa945c",
85
- "contracts/review-integration/v1/fixtures/status-corrupted.fixture.json": "a9c25d0d0eab2cc91ef095939c650deafadb112e75d0957a21aa8c60b51fdcda",
86
- "contracts/review-integration/v1/fixtures/status-unrelated.fixture.json": "e8aa372dfa582e4bf78992757135bc902c28bd9fe946210e40c3edf4c6abfb0b",
87
- "contracts/review-integration/v1/fixtures/status.fixture.json": "cf981c5fd715357f817fc9206523284dddd1ede8ceee99e7b4935847327e6adc",
88
+ "contracts/review-integration/v1/fixtures/final-verification-incident.fixture.json": "f8bc06549e62b0bee5cf2ecde625e18da178dd18c9d3023b7d7e8fd0ebbba646",
89
+ "contracts/review-integration/v1/fixtures/operation.fixture.json": "3547748a4df57382178064abbdb1cf12f1d58a75c0e9d6452fdd9beb3aaeac3a",
90
+ "contracts/review-integration/v1/fixtures/repair-preflight.fixture.json": "7168cb53ad470066d0b3edc3b7911d1aebff91abd41ecc3d822f8ffa5cea6cb1",
91
+ "contracts/review-integration/v1/fixtures/start-v2.fixture.json": "388c7c21374b89afe2d42d64bd1987d17ec0e2c7151cab1c56a08969ffb2ea0e",
92
+ "contracts/review-integration/v1/fixtures/start.fixture.json": "f369160ac26eb3427b57de2dd01c9d8c81e51c8a2bd546446780129d31b1945b",
93
+ "contracts/review-integration/v1/fixtures/status-ambiguous.fixture.json": "ee695fd58ba72adfb3b51dfd16432a177498173a45bfcb594d6bdc53bfa32e6e",
94
+ "contracts/review-integration/v1/fixtures/status-corrupted.fixture.json": "4cfc0048c28a39cec8a32fecfaad66e56e5c1248263ceb4ce66b6717981880b2",
95
+ "contracts/review-integration/v1/fixtures/status-recover.fixture.json": "714f762f72380ce93d567626cafbaa536ab3aae02af73d3d40ca123f1f30d8b0",
96
+ "contracts/review-integration/v1/fixtures/status-unrelated.fixture.json": "deab36c877ced3c9b480ca33724c10d88f75c761d6426fa14be850345122891d",
97
+ "contracts/review-integration/v1/fixtures/status-v2-ambiguous.fixture.json": "80a459a7a18d8d933dd42acb6a94a75ac19278e9a6c3b125e3017946768eaa47",
98
+ "contracts/review-integration/v1/fixtures/status-v2-corrupted.fixture.json": "466f1e28b101e95178630f26a90fff96ad3516e2aa6a17f5f357bab9bda2ab52",
99
+ "contracts/review-integration/v1/fixtures/status-v2-final-verification-retry.fixture.json": "889ede9a84bdbe561df2d2401adda95634582c44db9d1075ba839f33d65c7886",
100
+ "contracts/review-integration/v1/fixtures/status-v2-recover.fixture.json": "178331fc7177d2316fd4f56610ac295f7da2780be96b233b72935d5f476610f2",
101
+ "contracts/review-integration/v1/fixtures/status-v2-repair.fixture.json": "89083cad752fca38da09e919825d0b80641a8a029364ba1869e5b58ef2e59a1d",
102
+ "contracts/review-integration/v1/fixtures/status-v2-unrelated.fixture.json": "c178b338dcd5d30888acef37a9d752bd0932d6dedfffb61b0596a9cceabeb692",
103
+ "contracts/review-integration/v1/fixtures/status-v2.fixture.json": "5410d8bbae1b7152a43b3a5c4c880e9e98a5e47b76d910456f5ef13f19836f3a",
104
+ "contracts/review-integration/v1/fixtures/status.fixture.json": "555054d8046a896162995dcb117752f9cd1ef903fb9ebaad29af1b7e7f319bb3",
105
+ "contracts/review-integration/v1/fixtures/verification-evidence.fixture.json": "b30e3863548845b90e256d92193435d02da458bf8f15a4c33d023cd1f6894a5f",
106
+ "contracts/review-integration/v1/schemas/admitted-result.schema.json": "7796e8dbba331434594108c902dfab7ec46f691fa447a9259a78f2448111b0de",
107
+ "contracts/review-integration/v1/schemas/artifact-subject.schema.json": "f7dcd934e27e8f3735a37f3d0ec8048dd8ccc1811b9df61124a1dcbf8a03f40e",
108
+ "contracts/review-integration/v1/schemas/authority-repair-assessment.schema.json": "232591670009f99c53a68e91d1e7e60465c294f1721f493ab1e7ae182842cfb5",
88
109
  "contracts/review-integration/v1/schemas/capabilities-v1.1.schema.json": "2b14162284f375f8563e49d3a28caaa0aabb572094d8d290eb61844b1353af78",
110
+ "contracts/review-integration/v1/schemas/capabilities-v1.2.schema.json": "df1722adcd9c999edbef090bfd5d9a9713f6852a9bc9cb79684ef7c9c91c0d62",
111
+ "contracts/review-integration/v1/schemas/capabilities-v1.3.schema.json": "3401a062fa8a034ef7743f84adbfdd2ceadaf81bee8a7e62115fd4e18afacfcd",
112
+ "contracts/review-integration/v1/schemas/capabilities-v1.4.schema.json": "926b61c8ac0f870f09214f6bd8af1b035c5b72f14f0b83c0d4a7bdbb277f5447",
113
+ "contracts/review-integration/v1/schemas/capabilities-v1.5.schema.json": "abc783821524dcc33339495284805a85f79b3352efb7358c95016ed164bd7f24",
89
114
  "contracts/review-integration/v1/schemas/capabilities.schema.json": "ad333177494a251beac153f74bd751fa77126a9968aad69e64fc2abf15cff0f7",
90
- "contracts/review-integration/v1/schemas/failure.schema.json": "11284601a00e0192c41b1f3aab0b153635e28ad57ec5f1e2e90a69d129296c44",
91
- "contracts/review-integration/v1/schemas/operation.schema.json": "7673bb168b6df8cee68f4449a9b79f0a1fc349747e5b26009b41d649979f690d",
115
+ "contracts/review-integration/v1/schemas/consent.schema.json": "f8f2edec17568124488482c2aee399909111fe0cce2cba426fb29efd2c7c1cd0",
116
+ "contracts/review-integration/v1/schemas/failure.schema.json": "0ce29f61408fc21d72640fffdb215a608a820c29f3e5ff62d9cc295ed0451937",
117
+ "contracts/review-integration/v1/schemas/final-verification-incident.schema.json": "39b1ec178b1d3bc8da9a3d92dadd8092385000f2a6930b5bfcb4a84dbc6493ca",
118
+ "contracts/review-integration/v1/schemas/operation.schema.json": "6cf15b54977fea7301b9a5c766e709f8a912fda33560365de6dfcab4d2ecbce0",
92
119
  "contracts/review-integration/v1/schemas/projection.schema.json": "7168a3eba929dde2b8f0b7723ee51d5a5421102bdeefe892578c263debd08db2",
120
+ "contracts/review-integration/v1/schemas/repair.schema.json": "febb747dc68f8ded5974b6dd94051ec3cfdb5a886ccb853b1ce53aaf2e41efc0",
121
+ "contracts/review-integration/v1/schemas/result-artifact-v2.schema.json": "38895aae2f6ca4980b1a8e157fee8503920820d5f6be3c757c0fa04e8430cd6b",
93
122
  "contracts/review-integration/v1/schemas/result-artifact.schema.json": "91296bd2c261fd2fe03bffd63efe58badd4927e0d0d8480cd4213f651ecacdf6",
94
- "contracts/review-integration/v1/schemas/start.schema.json": "f3390d09cccb5136392e247db780b9415ceba8c49971a1b1a84240fc66694d97",
95
- "contracts/review-integration/v1/schemas/status.schema.json": "11ed3aae66e86534b696df3ed11536c4d1f648e2d33ee05a1cd48412903d5db2",
96
- "contracts/review-integration/v1/fixtures/status-recover.fixture.json": "e50893555694944fc904f3c90442c2f4b2855fda1f36f55482c5a7eed884a75a",
97
- "docs/review-integration.md": "4bead7c3e3cf7ecfac18941d96ece52a1d6eb263d410ef54f9422aa07eab131c",
123
+ "contracts/review-integration/v1/schemas/start-v2.schema.json": "ec8550cd93bbe84af1ce87dfd7abfa9e24692f42b20f8f0bf9cac1d4b88ea46c",
124
+ "contracts/review-integration/v1/schemas/start.schema.json": "4296aebbd4128ce51945a2f6d3228aa77ac7215c802978d559bff5279ec56229",
125
+ "contracts/review-integration/v1/schemas/status-v2.schema.json": "63e8988ce276d948ea8305008a3d27c4e26dff664cf54fffed9e188f465d92d1",
126
+ "contracts/review-integration/v1/schemas/status.schema.json": "67f3bddf5f5feeb3213bce489de8548546163b2e1d49a0e3965c0091dabc8c39",
127
+ "contracts/review-integration/v1/schemas/targeted-validation-request.schema.json": "52b91154693b4dd66983fc91ecf7197503555f2c9e85cac626cffd3035c53d65",
128
+ "contracts/review-integration/v1/schemas/verification-evidence.schema.json": "fd15890bf2ef1db95d771ee7f468e9e64014351d7940f65604eb24f41e68a22f",
129
+ "contracts/review-integration/v2/fixtures/capabilities.fixture.json": "17c150d851c15b3f0c20d18c2e2741eb2232ffa24f35aa71d6d30e90a85e42b7",
130
+ "contracts/review-integration/v2/fixtures/consent.fixture.json": "a6d3ddc979d3dba202e4bf4ea5ca468ade1284f564f9325c313f4f9a9796f0ad",
131
+ "contracts/review-integration/v2/fixtures/start.fixture.json": "34b21328fa910e03af2fac8e816c5b510ba146201da0cd4ef1693d39a6344ba0",
132
+ "contracts/review-integration/v2/fixtures/status.fixture.json": "d5438578f2969f17635fecea94c7ef46d14c78fa668e50df48c4254254d5e935",
133
+ "contracts/review-integration/v2/schemas/admitted-result.schema.json": "c6a9c880191d65c46d9cfc8a0812af16b636573a8f6e57ea34aa16d6f6bb9735",
134
+ "contracts/review-integration/v2/schemas/artifact-subject.schema.json": "3e71a81340ea6149b03afa71530d10ce654c415fa21d4e07f0c9c25b3d2d70a3",
135
+ "contracts/review-integration/v2/schemas/capabilities.schema.json": "7ab061ed27bd3b929d6033cc20f56097e851f4454ca14a815255748b50191248",
136
+ "contracts/review-integration/v2/schemas/consent.schema.json": "b2b4465338497f11927de91cb2e5da12b6cb4a1039afe05aebe1abbf53b21858",
137
+ "contracts/review-integration/v2/schemas/failure.schema.json": "1c53da8c543cc5e03fbbfc700d4fc80cadb3b0e5e72c6d77a7790008671d0962",
138
+ "contracts/review-integration/v2/schemas/operation.schema.json": "beee02046ec1d5b8c89e3d7de35f897c275830e43c67ecd162886bfd0138ace6",
139
+ "contracts/review-integration/v2/schemas/repair.schema.json": "9e842d1ded797a91ede16b2054a38708b2d1fc7a0d6217541f84c0f2f5a2e73e",
140
+ "contracts/review-integration/v2/schemas/start.schema.json": "86ddfcfe1912489abef7461613d692ea53a4d7d73d444d5f6dbbd7f9866a2c37",
141
+ "contracts/review-integration/v2/schemas/status.schema.json": "132b15dd7a0514aa451373bd4b3c02a1491b7b11cf5b9ce43bc300b549397ac9",
142
+ "docs/review-integration.md": "4ee3493303b03c7cb761f35865722b1691f164f72e6f94574512c42832ff04f2",
98
143
  };
99
144
 
100
145
  requiredPaths.push(...Object.keys(contractHashes));
101
146
 
102
- const missing = requiredPaths.filter((relativePath) => {
103
- const absolutePath = join(root, relativePath);
104
- return !existsSync(absolutePath) || !statSync(absolutePath).isFile();
105
- });
106
-
107
- if (missing.length > 0) {
108
- console.error("gentle-pi package is missing required Pi resources:");
109
- for (const relativePath of missing) {
110
- console.error(`- ${relativePath}`);
111
- }
112
- console.error("\nRefusing to pack/publish an incomplete npm package.");
113
- process.exit(1);
147
+ function listFilesRecursively(directory) {
148
+ return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
149
+ const absolutePath = join(directory, entry.name);
150
+ if (entry.isDirectory()) return listFilesRecursively(absolutePath);
151
+ return entry.isFile() ? [absolutePath] : [];
152
+ });
114
153
  }
115
154
 
116
- const driftedContracts = Object.entries(contractHashes).flatMap(([relativePath, expected]) => {
117
- const actual = createHash("sha256").update(readFileSync(join(root, relativePath))).digest("hex");
118
- return actual === expected ? [] : [{ relativePath, expected, actual }];
119
- });
155
+ // Walks `contracts/` on disk and reconciles it against `contractHashes`
156
+ // (restricted to `contracts/**` keys — `docs/review-integration.md` is a
157
+ // byte-pinned contract artifact but lives outside this walk root). Reports
158
+ // the two drift directions separately so a new unlisted file and a stale
159
+ // hash-map entry are both visible.
160
+ export function reconcileContractsOnDisk(packageRoot, hashes) {
161
+ const contractsRoot = join(packageRoot, "contracts");
162
+ const walked = existsSync(contractsRoot)
163
+ ? listFilesRecursively(contractsRoot).map((absolutePath) =>
164
+ relative(packageRoot, absolutePath).split(sep).join("/"),
165
+ )
166
+ : [];
167
+ const listed = Object.keys(hashes).filter((relativePath) => relativePath.startsWith("contracts/"));
168
+ const walkedSet = new Set(walked);
169
+ const listedSet = new Set(listed);
120
170
 
121
- if (driftedContracts.length > 0) {
122
- console.error("gentle-pi packaged review-integration/v1 bytes drifted from the byte-identical Gentle AI v2.1.11 contract:");
123
- for (const drift of driftedContracts) console.error(`- ${drift.relativePath}: expected ${drift.expected}, got ${drift.actual}`);
124
- process.exit(1);
171
+ return {
172
+ unlistedOnDisk: walked.filter((relativePath) => !listedSet.has(relativePath)).sort(),
173
+ listedButMissing: listed.filter((relativePath) => !walkedSet.has(relativePath)).sort(),
174
+ };
125
175
  }
126
176
 
127
- // Release guard: refuse to pack/publish while any installer digest is not a real
128
- // pinned SHA-256 (for example the pre-release pending sentinel).
129
- const { GENTLE_AI_RELEASE_ASSETS } = await import(new URL("./gentle-ai-installer.mjs", import.meta.url));
130
- const unpinnedDigests = Object.entries(GENTLE_AI_RELEASE_ASSETS).flatMap(([target, asset]) =>
131
- [["sha256", asset.sha256], ["binarySha256", asset.binarySha256]]
132
- .filter(([, digest]) => !/^[0-9a-f]{64}$/.test(digest))
133
- .map(([field]) => `${target}.${field}`));
134
- if (unpinnedDigests.length > 0) {
135
- console.error("gentle-pi Gentle AI release digests are not pinned SHA-256 values:");
136
- for (const entry of unpinnedDigests) console.error(`- ${entry}`);
137
- console.error("Refusing to pack/publish until scripts/gentle-ai-installer.mjs pins the published checksums.txt archive digests and extracted binary digests.");
138
- process.exit(1);
177
+ // Reads the generator's `sources` array by regex rather than importing it,
178
+ // so this script never needs the generator to export anything it doesn't
179
+ // already export for its own `--write`/`--check` CLI use.
180
+ export function extractGeneratedRuntimeSources(packageRoot) {
181
+ const generatorPath = join(packageRoot, "scripts/build-git-commit-transaction-runner.mjs");
182
+ const generatorSource = readFileSync(generatorPath, "utf8");
183
+ const sourcesMatch = generatorSource.match(/const sources = \[([\s\S]*?)\];/);
184
+ if (!sourcesMatch) {
185
+ throw new Error(`${generatorPath} does not declare a "sources" array`);
186
+ }
187
+ return [...sourcesMatch[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]);
139
188
  }
140
189
 
141
- const generatedRuntimeCheck = spawnSync(process.execPath, [join(root, "scripts/build-git-commit-transaction-runner.mjs"), "--check"], {
142
- cwd: root,
143
- encoding: "utf8",
144
- env: { ...process.env, NODE_NO_WARNINGS: "1" },
145
- });
146
- if (generatedRuntimeCheck.status !== 0) {
147
- console.error("gentle-pi generated commit transaction runtime does not match its TypeScript sources:");
148
- console.error((generatedRuntimeCheck.stderr || generatedRuntimeCheck.stdout || "unknown generator failure").trim());
149
- process.exit(1);
190
+ // Three-way reconciliation: the generator's `sources` names must equal the
191
+ // `.mjs` basenames on disk in `runtime/`, which must equal the `runtime/`
192
+ // entries in `requiredPaths`. Deliberately not a `lib/`-driven walk: most
193
+ // `lib/` modules are intentionally unpaired with a generated runtime file.
194
+ export function reconcileGeneratedRuntimeSources(packageRoot, sources, paths) {
195
+ const runtimeRoot = join(packageRoot, "runtime");
196
+ const runtimeBasenames = existsSync(runtimeRoot)
197
+ ? readdirSync(runtimeRoot)
198
+ .filter((name) => name.endsWith(".mjs"))
199
+ .map((name) => name.slice(0, -".mjs".length))
200
+ : [];
201
+ const requiredRuntimeBasenames = paths
202
+ .filter((relativePath) => relativePath.startsWith("runtime/") && relativePath.endsWith(".mjs"))
203
+ .map((relativePath) => relativePath.slice("runtime/".length, -".mjs".length));
204
+
205
+ const sourceSet = new Set(sources);
206
+ const runtimeSet = new Set(runtimeBasenames);
207
+ const requiredSet = new Set(requiredRuntimeBasenames);
208
+ const names = new Set([...sourceSet, ...runtimeSet, ...requiredSet]);
209
+
210
+ const drifted = [...names]
211
+ .filter((name) => !(sourceSet.has(name) && runtimeSet.has(name) && requiredSet.has(name)))
212
+ .sort()
213
+ .map((name) => ({
214
+ name,
215
+ inSources: sourceSet.has(name),
216
+ inRuntimeDir: runtimeSet.has(name),
217
+ inRequiredPaths: requiredSet.has(name),
218
+ }));
219
+
220
+ return { drifted };
150
221
  }
151
222
 
152
- const installer = readFileSync(join(root, "scripts/gentle-ai-installer.mjs"), "utf8");
153
- const binaryResolver = readFileSync(join(root, "lib/gentle-ai-binary.ts"), "utf8");
154
- if (!installer.includes('INSTALLER_VERSION = "2.1.11"') || !binaryResolver.includes('GENTLE_AI_VERSION = "2.1.11"')) {
155
- console.error("gentle-pi package-local Gentle AI version pins are not both v2.1.11.");
156
- process.exit(1);
223
+ async function main() {
224
+ const missing = requiredPaths.filter((relativePath) => {
225
+ const absolutePath = join(root, relativePath);
226
+ return !existsSync(absolutePath) || !statSync(absolutePath).isFile();
227
+ });
228
+
229
+ if (missing.length > 0) {
230
+ console.error("gentle-pi package is missing required Pi resources:");
231
+ for (const relativePath of missing) {
232
+ console.error(`- ${relativePath}`);
233
+ }
234
+ console.error("\nRefusing to pack/publish an incomplete npm package.");
235
+ process.exit(1);
236
+ }
237
+
238
+ const { unlistedOnDisk, listedButMissing } = reconcileContractsOnDisk(root, contractHashes);
239
+ if (unlistedOnDisk.length > 0 || listedButMissing.length > 0) {
240
+ console.error("gentle-pi packaged contracts/ tree has drifted from contractHashes:");
241
+ for (const relativePath of unlistedOnDisk) console.error(`- unlisted-on-disk: ${relativePath}`);
242
+ for (const relativePath of listedButMissing) console.error(`- listed-but-missing: ${relativePath}`);
243
+ console.error("\nRefusing to pack/publish an unreconciled contracts/ tree.");
244
+ process.exit(1);
245
+ }
246
+
247
+ const generatedRuntimeSources = extractGeneratedRuntimeSources(root);
248
+ const { drifted } = reconcileGeneratedRuntimeSources(root, generatedRuntimeSources, requiredPaths);
249
+ if (drifted.length > 0) {
250
+ console.error("gentle-pi generated commit transaction runtime sources, runtime/*.mjs, and requiredPaths have drifted apart:");
251
+ for (const entry of drifted) {
252
+ const where = [];
253
+ if (!entry.inSources) where.push("missing from generator sources");
254
+ if (!entry.inRuntimeDir) where.push("missing from runtime/*.mjs");
255
+ if (!entry.inRequiredPaths) where.push("missing from requiredPaths");
256
+ console.error(`- ${entry.name}: ${where.join(", ")}`);
257
+ }
258
+ console.error("\nRefusing to pack/publish an unreconciled generated runtime.");
259
+ process.exit(1);
260
+ }
261
+
262
+ const driftedContracts = Object.entries(contractHashes).flatMap(([relativePath, expected]) => {
263
+ const actual = createHash("sha256").update(readFileSync(join(root, relativePath))).digest("hex");
264
+ return actual === expected ? [] : [{ relativePath, expected, actual }];
265
+ });
266
+
267
+ if (driftedContracts.length > 0) {
268
+ console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the byte-identical Gentle AI v2.2.2 contract:");
269
+ for (const drift of driftedContracts) console.error(`- ${drift.relativePath}: expected ${drift.expected}, got ${drift.actual}`);
270
+ process.exit(1);
271
+ }
272
+
273
+ // Release guard: refuse to pack/publish while any installer digest is not a real
274
+ // pinned SHA-256 (for example the pre-release pending sentinel).
275
+ const { GENTLE_AI_RELEASE_ASSETS } = await import(new URL("./gentle-ai-installer.mjs", import.meta.url));
276
+ const unpinnedDigests = Object.entries(GENTLE_AI_RELEASE_ASSETS).flatMap(([target, asset]) =>
277
+ [["sha256", asset.sha256], ["binarySha256", asset.binarySha256]]
278
+ .filter(([, digest]) => !/^[0-9a-f]{64}$/.test(digest))
279
+ .map(([field]) => `${target}.${field}`));
280
+ if (unpinnedDigests.length > 0) {
281
+ console.error("gentle-pi Gentle AI release digests are not pinned SHA-256 values:");
282
+ for (const entry of unpinnedDigests) console.error(`- ${entry}`);
283
+ console.error("Refusing to pack/publish until scripts/gentle-ai-installer.mjs pins the published checksums.txt archive digests and extracted binary digests.");
284
+ process.exit(1);
285
+ }
286
+
287
+ const generatedRuntimeCheck = spawnSync(process.execPath, [join(root, "scripts/build-git-commit-transaction-runner.mjs"), "--check"], {
288
+ cwd: root,
289
+ encoding: "utf8",
290
+ env: { ...process.env, NODE_NO_WARNINGS: "1" },
291
+ });
292
+ if (generatedRuntimeCheck.status !== 0) {
293
+ console.error("gentle-pi generated commit transaction runtime does not match its TypeScript sources:");
294
+ console.error((generatedRuntimeCheck.stderr || generatedRuntimeCheck.stdout || "unknown generator failure").trim());
295
+ process.exit(1);
296
+ }
297
+
298
+ const installer = readFileSync(join(root, "scripts/gentle-ai-installer.mjs"), "utf8");
299
+ const binaryResolver = readFileSync(join(root, "lib/gentle-ai-binary.ts"), "utf8");
300
+ if (!installer.includes('INSTALLER_VERSION = "2.2.2"') || !binaryResolver.includes('GENTLE_AI_VERSION = "2.2.2"')) {
301
+ console.error("gentle-pi package-local Gentle AI version pins are not both v2.2.2.");
302
+ process.exit(1);
303
+ }
304
+
305
+ console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-identical v2.2.2 contract artifacts).`);
157
306
  }
158
307
 
159
- console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-identical v2.1.11 contract artifacts).`);
308
+ const isMainModule = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
309
+ if (isMainModule) {
310
+ await main();
311
+ }
@@ -4,7 +4,7 @@ description: "Release gentle-pi through GitHub and npm. Trigger: release, publis
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: gentleman-programming
7
- version: "1.0"
7
+ version: "1.2"
8
8
  ---
9
9
 
10
10
  ## When to Use
@@ -15,6 +15,7 @@ Use this skill when preparing, publishing, or verifying a `gentle-pi` release.
15
15
 
16
16
  - Do not publish `gentle-pi` to npm from a local machine.
17
17
  - npm publishing MUST go through the GitHub Actions workflow `.github/workflows/publish.yml` so provenance, environment protection, and registry credentials are controlled by GitHub.
18
+ - Dispatch the trusted workflow definition from protected default `main`, never from a release tag. Its only caller input is the exact annotated version tag.
18
19
  - Use a clean worktree for release commits. Do not package unrelated local files or scratch artifacts.
19
20
  - Validate the approved receipt against the exact immutable release target with zero review actors before publication.
20
21
  - Release from protected `main` may bypass receipt validation only when the tag targets the current immutable `origin/main` SHA, required CI for that exact SHA is successful, the remote head is rechecked before tag push, and no fresh risk evidence exists; otherwise fail closed through native receipt validation. Never infer the tag target from local `HEAD`. Major and post-incident releases require explicit extraordinary review even when fast-path checks pass.
@@ -40,10 +41,11 @@ Use this skill when preparing, publishing, or verifying a `gentle-pi` release.
40
41
 
41
42
  ```bash
42
43
  pnpm test
43
- pnpm publish --dry-run --no-git-checks
44
+ node scripts/verify-package-files.mjs
45
+ npm pack --dry-run
44
46
  ```
45
47
 
46
- The dry run is allowed because it does not publish. It verifies package contents and lifecycle scripts.
48
+ `npm pack --dry-run` verifies package contents and lifecycle scripts without entering a publish path.
47
49
 
48
50
  4. **Commit and push**
49
51
 
@@ -51,28 +53,51 @@ Use this skill when preparing, publishing, or verifying a `gentle-pi` release.
51
53
  git add <intended-files>
52
54
  git commit -m "<type(scope): release-ready change>"
53
55
  git push origin HEAD:main
56
+ git fetch origin main --tags
54
57
  ```
55
58
 
56
- 5. **Create the GitHub release**
59
+ 5. **Create and verify the exact version tag**
57
60
 
58
61
  ```bash
59
- git tag -a v<version> -m "gentle-pi v<version>"
60
- git push origin v<version>
61
- gh release create v<version> \
62
+ version="$(node -p "require('./package.json').version")"
63
+ tag="v${version}"
64
+ release_sha="$(git rev-parse 'origin/main^{commit}')"
65
+
66
+ test "$(git rev-parse 'HEAD^{commit}')" = "${release_sha}"
67
+ test -z "$(git ls-remote --tags origin "refs/tags/${tag}")"
68
+
69
+ git tag -a "${tag}" "${release_sha}" -m "gentle-pi ${tag}"
70
+ test "$(git rev-parse "${tag}^{commit}")" = "${release_sha}"
71
+
72
+ git fetch origin main
73
+ test "$(git rev-parse 'origin/main^{commit}')" = "${release_sha}"
74
+ git push origin "refs/tags/${tag}"
75
+
76
+ git fetch --no-tags origin "refs/tags/${tag}"
77
+ test "$(git rev-parse 'FETCH_HEAD^{commit}')" = "${release_sha}"
78
+
79
+ gh release create "${tag}" \
62
80
  --repo Gentleman-Programming/gentle-pi \
63
- --title "gentle-pi v<version>" \
81
+ --verify-tag \
82
+ --title "gentle-pi ${tag}" \
64
83
  --notes "<release notes>"
65
84
  ```
66
85
 
86
+ Do not retag or overwrite an existing version. The tag target comes from the freshly fetched immutable `origin/main` commit, not an ambient local branch.
87
+
67
88
  6. **Publish npm through GitHub Actions**
68
89
 
69
90
  ```bash
91
+ version="$(node -p "require('./package.json').version")"
92
+ tag="v${version}"
70
93
  gh workflow run publish.yml \
71
94
  --repo Gentleman-Programming/gentle-pi \
72
95
  --ref main \
73
- -f dist-tag=latest
96
+ -f tag="${tag}"
74
97
  ```
75
98
 
99
+ The workflow definition always comes from protected default `main`. It accepts only one exact `vSemVer` tag, fetches the remote annotated tag and current remote `main`, and requires the peeled tag commit, dispatch/main workflow commit, checkout, and `package.json` version to match. It re-queries remote tag and `main` immediately before npm publication, derives the dist-tag internally, and uses trusted OIDC with provenance.
100
+
76
101
  Watch the run and fail the release if it fails:
77
102
 
78
103
  ```bash
@@ -90,7 +115,8 @@ Use this skill when preparing, publishing, or verifying a `gentle-pi` release.
90
115
  ## Failure Handling
91
116
 
92
117
  - A publication failure never reopens the closed review lineage. Diagnose and retry publication separately without resetting review counters.
93
- - If a local `npm publish` fails, do not retry locally. Use the GitHub workflow instead.
118
+ - Never attempt or retry `npm publish` locally. Re-dispatch from trusted `main` only when the same tag still targets the current remote `main` and the failure was publication-only.
119
+ - If remote `main` advances, do not move or recreate the existing tag. Prepare a new release commit/version and create a new annotated version tag.
94
120
  - If the workflow fails, inspect logs with:
95
121
 
96
122
  ```bash
@@ -104,7 +130,8 @@ Use this skill when preparing, publishing, or verifying a `gentle-pi` release.
104
130
  Report:
105
131
 
106
132
  - Commit SHA pushed to `main`.
133
+ - Exact version tag and its peeled commit SHA.
107
134
  - GitHub release URL.
108
135
  - Publish workflow run URL and conclusion.
109
- - npm exact version and `latest` dist-tag.
136
+ - npm exact version and the workflow-derived dist-tag (`latest`, `beta`, or `next`).
110
137
  - Any remaining follow-up or warnings.