@bradheitmann/odin-sentinel 0.4.12 → 0.5.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 (92) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +24 -17
  3. package/dist/src/harness-pacing/index.d.ts +10 -0
  4. package/dist/src/harness-pacing/index.js +11 -0
  5. package/dist/src/harness-pacing/index.js.map +1 -0
  6. package/dist/src/harness-pacing/recommend.d.ts +28 -0
  7. package/dist/src/harness-pacing/recommend.js +74 -0
  8. package/dist/src/harness-pacing/recommend.js.map +1 -0
  9. package/dist/src/harness-pacing/schema.d.ts +28 -0
  10. package/dist/src/harness-pacing/schema.js +2 -0
  11. package/dist/src/harness-pacing/schema.js.map +1 -0
  12. package/dist/src/harness-pacing/storage.d.ts +32 -0
  13. package/dist/src/harness-pacing/storage.js +74 -0
  14. package/dist/src/harness-pacing/storage.js.map +1 -0
  15. package/dist/src/mcp/server.js +29 -2
  16. package/dist/src/mcp/server.js.map +1 -1
  17. package/dist/src/odin-watch/backends/cmux.d.ts +6 -0
  18. package/dist/src/odin-watch/backends/cmux.js +39 -0
  19. package/dist/src/odin-watch/backends/cmux.js.map +1 -0
  20. package/dist/src/odin-watch/backends/tmux.d.ts +6 -0
  21. package/dist/src/odin-watch/backends/tmux.js +40 -0
  22. package/dist/src/odin-watch/backends/tmux.js.map +1 -0
  23. package/dist/src/odin-watch/classifier.d.ts +27 -0
  24. package/dist/src/odin-watch/classifier.js +182 -0
  25. package/dist/src/odin-watch/classifier.js.map +1 -0
  26. package/dist/src/odin-watch/index.d.ts +2 -0
  27. package/dist/src/odin-watch/index.js +200 -0
  28. package/dist/src/odin-watch/index.js.map +1 -0
  29. package/dist/src/odin-watch/snapshotter.d.ts +11 -0
  30. package/dist/src/odin-watch/snapshotter.js +2 -0
  31. package/dist/src/odin-watch/snapshotter.js.map +1 -0
  32. package/dist/src/odin-watch/writers.d.ts +8 -0
  33. package/dist/src/odin-watch/writers.js +27 -0
  34. package/dist/src/odin-watch/writers.js.map +1 -0
  35. package/dist/src/protocol/index.d.ts +3 -1
  36. package/dist/src/protocol/index.js +4 -1
  37. package/dist/src/protocol/index.js.map +1 -1
  38. package/dist/src/protocol/repository.d.ts +14 -0
  39. package/dist/src/protocol/repository.js +25 -1
  40. package/dist/src/protocol/repository.js.map +1 -1
  41. package/dist/src/protocol/schemas.d.ts +144 -0
  42. package/dist/src/protocol/schemas.js +23 -0
  43. package/dist/src/protocol/schemas.js.map +1 -1
  44. package/dist/src/protocol/service.d.ts +19 -2
  45. package/dist/src/protocol/service.js +89 -3
  46. package/dist/src/protocol/service.js.map +1 -1
  47. package/dist/src/protocol/surface-layout.d.ts +20 -0
  48. package/dist/src/protocol/surface-layout.js +20 -0
  49. package/dist/src/protocol/surface-layout.js.map +1 -1
  50. package/dist/src/protocol/version.d.ts +2 -2
  51. package/dist/src/protocol/version.js +2 -2
  52. package/dist/src/protocol/version.js.map +1 -1
  53. package/dist/src/utils/execFileNoThrow.d.ts +5 -0
  54. package/dist/src/utils/execFileNoThrow.js +18 -0
  55. package/dist/src/utils/execFileNoThrow.js.map +1 -0
  56. package/docs/adapters/cmux-adapter.md +168 -0
  57. package/docs/adapters/herdr-adapter.md +150 -0
  58. package/docs/adapters/minimux-adapter.md +152 -0
  59. package/docs/adapters/plain-terminal.md +80 -0
  60. package/docs/adapters/tmux-adapter.md +150 -0
  61. package/docs/guides/quick-start.md +7 -7
  62. package/docs/guides/quickstart-prompts.md +4 -4
  63. package/docs/lattice/odin-lattice-design.md +555 -0
  64. package/docs/reference/distribution.md +11 -5
  65. package/docs/reference/public-surface-audit.md +3 -3
  66. package/package.json +7 -5
  67. package/plugins/odin-scp/.claude-plugin/plugin.json +2 -2
  68. package/plugins/odin-scp/README.md +6 -6
  69. package/plugins/odin-scp/skills/odin-scp/CHANGELOG.md +12 -0
  70. package/plugins/odin-scp/skills/odin-scp/SKILL.md +196 -3
  71. package/plugins/odin-scp/skills/odin-scp/references/canonical-introduction-prompt.md +0 -2
  72. package/protocol/SCP.md +2 -2
  73. package/protocol/bootstrap-skill.md +196 -3
  74. package/protocol/closeout.yaml +1 -1
  75. package/protocol/delegation.yaml +1 -1
  76. package/protocol/mission-frontrun/droids-scrutiny-feature-reviewer.md +70 -0
  77. package/protocol/mission-frontrun/orchestrator-contract.md +70 -0
  78. package/protocol/mission-frontrun/scrutiny-feature-reviewer-contract.md +73 -0
  79. package/protocol/mission-frontrun/scrutiny-validator-contract.md +77 -0
  80. package/protocol/mission-frontrun/worker-contract.md +66 -0
  81. package/protocol/model-profiles.yaml +8 -1
  82. package/protocol/receipts/boot-receipt.yaml +13 -0
  83. package/protocol/role-cards/dev-worker.md +74 -0
  84. package/protocol/role-cards/exec-asst.md +83 -0
  85. package/protocol/role-cards/exec-pm.md +66 -0
  86. package/protocol/role-cards/qa-worker.md +71 -0
  87. package/protocol/role-cards/team-pm.md +67 -0
  88. package/protocol/roles.yaml +1 -1
  89. package/protocol/skill-references/canonical-introduction-prompt.md +0 -2
  90. package/protocol/topology.yaml +1 -1
  91. package/scripts/audit/public-surface.mjs +27 -2
  92. package/scripts/audit/verify-pack.mjs +121 -5
@@ -1,4 +1,4 @@
1
- version: 0.4.12
1
+ version: 0.5.0
2
2
  roles:
3
3
  EXEC_PM:
4
4
  title: EXEC PM
@@ -12,7 +12,6 @@ Use the `odin-scp` skill if available. Also read local project authority files w
12
12
  - CLAUDE.md
13
13
  - config/constitutional/constitutional-agent.md
14
14
  - project-local governance or constitution files declared by the repository
15
- - docs/handoffs/
16
15
  - .odin/handoffs/
17
16
  - .odin/audit/
18
17
 
@@ -41,7 +40,6 @@ Phase 0 - live preflight:
41
40
  - git rev-parse HEAD
42
41
  - git rev-parse @{u}, if upstream exists
43
42
  2. Discover handoffs and audit state:
44
- - docs/handoffs/
45
43
  - .odin/handoffs/
46
44
  - .odin/audit/
47
45
  3. If no handoff exists, treat the repo as a fresh SCP bootstrap.
@@ -1,4 +1,4 @@
1
- version: 0.4.12
1
+ version: 0.5.0
2
2
  default_topology:
3
3
  executive_office:
4
4
  team: A
@@ -1,5 +1,5 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
 
@@ -18,6 +18,7 @@ const PUBLIC_ROOTS = [
18
18
  ];
19
19
 
20
20
  const EXCLUDED_PREFIXES = [".git/", "dist/", "node_modules/", "project/" + "planning" + "/", "." + "edge-" + "agentic" + "/local/", "tests/"];
21
+ export const FORBIDDEN_PUBLIC_PREFIXES = ["docs/handoffs/"];
21
22
 
22
23
  function walk(dir) {
23
24
  return readdirSync(dir).flatMap((entry) => {
@@ -47,6 +48,7 @@ function filesToAudit() {
47
48
  return `${tracked}\n${untracked}`
48
49
  .split("\n")
49
50
  .filter(Boolean)
51
+ .filter((file) => existsSync(file))
50
52
  .filter((file) => file !== "pnpm-lock.yaml")
51
53
  .filter(isPublicAuditFile);
52
54
  } catch {
@@ -54,6 +56,13 @@ function filesToAudit() {
54
56
  }
55
57
  }
56
58
 
59
+ function forbiddenPublicPathFindings() {
60
+ return FORBIDDEN_PUBLIC_PREFIXES.flatMap((prefix) => {
61
+ if (!existsSync(prefix)) return [];
62
+ return walk(prefix).map((file) => `${file}: internal handoff files must not exist under public docs`);
63
+ });
64
+ }
65
+
57
66
  const BUNDLED_DOC = new Set([
58
67
  "README.md",
59
68
  "docs/guides/quickstart-prompts.md",
@@ -78,13 +87,20 @@ const forbidden = [
78
87
  "i"
79
88
  )
80
89
  },
81
- { name: "secret-looking assignment", pattern: /(api[_-]?key|secret|token|password)\s*[:=]\s*["'][^"']+["']/i }
90
+ { name: "secret-looking quoted assignment", pattern: /(api[_-]?key|secret|token|password)\s*[:=]\s*["'][^"']+["']/i },
91
+ { name: "secret-looking unquoted assignment", pattern: /(api[_-]?key|secret|token|password)\s*[:=]\s*[A-Za-z0-9._~+/=-]{16,}/i },
92
+ { name: "bearer token literal", pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{20,}/i },
93
+ { name: "URI credential literal", pattern: /[a-z][a-z0-9+.-]*:\/\/[^/\s:@]+:[^/\s:@]+@/i }
82
94
  ];
83
95
 
84
96
  export function auditPublicSurface(fileTextByPath) {
85
97
  const findings = [];
86
98
  for (const [file, text] of Object.entries(fileTextByPath)) {
87
99
  if (!isPublicAuditFile(file)) continue;
100
+ if (FORBIDDEN_PUBLIC_PREFIXES.some((prefix) => file.startsWith(prefix))) {
101
+ findings.push(`${file}: internal handoff files must not exist under public docs`);
102
+ continue;
103
+ }
88
104
  for (const rule of forbidden) {
89
105
  if (rule.exemptFiles?.has(file)) continue;
90
106
  if (rule.pattern.test(text)) findings.push(`${file}: ${rule.name}`);
@@ -95,6 +111,15 @@ export function auditPublicSurface(fileTextByPath) {
95
111
 
96
112
  export function main() {
97
113
  const publicFiles = filesToAudit();
114
+ const forbiddenPublicFiles = publicFiles.filter((file) => FORBIDDEN_PUBLIC_PREFIXES.some((prefix) => file.startsWith(prefix)));
115
+ const forbiddenPathFindings = forbiddenPublicPathFindings();
116
+ if (forbiddenPublicFiles.length > 0) {
117
+ throw new Error(`Public surface audit failed: internal handoff files are public:\n${forbiddenPublicFiles.join("\n")}`);
118
+ }
119
+ if (forbiddenPathFindings.length > 0) {
120
+ throw new Error(`Public surface audit failed:\n${forbiddenPathFindings.join("\n")}`);
121
+ }
122
+
98
123
  const findings = auditPublicSurface(Object.fromEntries(publicFiles.map((file) => [file, readFileSync(file, "utf8")])));
99
124
 
100
125
  if (findings.length > 0) {
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { readFileSync } from "node:fs";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { join } from "node:path";
3
4
  import { pathToFileURL } from "node:url";
4
5
 
5
6
  export const MINIMUM_COMPATIBLE_CHILD_MCP_VERSION = "0.4.5";
@@ -17,7 +18,17 @@ const requiredProtocolFiles = [
17
18
  "protocol/skill-references/boot-receipt-examples.md",
18
19
  "protocol/skill-references/canonical-introduction-prompt.md",
19
20
  "protocol/skill-references/harness-skill-targets.md",
20
- "protocol/skill-references/team-bootstrap-runbook.md"
21
+ "protocol/skill-references/team-bootstrap-runbook.md",
22
+ "protocol/role-cards/exec-pm.md",
23
+ "protocol/role-cards/team-pm.md",
24
+ "protocol/role-cards/dev-worker.md",
25
+ "protocol/role-cards/qa-worker.md",
26
+ "protocol/role-cards/exec-asst.md",
27
+ "protocol/mission-frontrun/orchestrator-contract.md",
28
+ "protocol/mission-frontrun/worker-contract.md",
29
+ "protocol/mission-frontrun/scrutiny-validator-contract.md",
30
+ "protocol/mission-frontrun/scrutiny-feature-reviewer-contract.md",
31
+ "protocol/mission-frontrun/droids-scrutiny-feature-reviewer.md"
21
32
  ];
22
33
 
23
34
  const requiredTemplateFiles = [
@@ -44,6 +55,12 @@ export const requiredPackageFiles = [
44
55
  "docs/reference/cost-and-privacy.md",
45
56
  "docs/reference/distribution.md",
46
57
  "docs/reference/public-surface-audit.md",
58
+ "docs/lattice/odin-lattice-design.md",
59
+ "docs/adapters/cmux-adapter.md",
60
+ "docs/adapters/tmux-adapter.md",
61
+ "docs/adapters/minimux-adapter.md",
62
+ "docs/adapters/herdr-adapter.md",
63
+ "docs/adapters/plain-terminal.md",
47
64
  ...requiredProtocolFiles,
48
65
  "plugins/odin-scp/.claude-plugin/plugin.json",
49
66
  "plugins/odin-scp/skills/odin-scp/SKILL.md",
@@ -58,6 +75,9 @@ export const requiredPackageFiles = [
58
75
  ...requiredTemplateFiles,
59
76
  "scripts/audit/public-surface.mjs",
60
77
  "scripts/audit/verify-pack.mjs",
78
+ "scripts/protocol/install-activation-hooks.mjs",
79
+ "scripts/protocol/verify-governed-context.mjs",
80
+ "scripts/protocol/verify-instruction-read.mjs",
61
81
  "AGENTS.md",
62
82
  "CLAUDE.md",
63
83
  "README.md",
@@ -82,14 +102,20 @@ const protocolResourceVersionLockedFiles = new Set([
82
102
  "protocol/topology.yaml"
83
103
  ]);
84
104
 
85
- const forbiddenPackagePrefixes = ["project/" + "planning" + "/", "." + "edge-" + "agentic" + "/local/"];
105
+ const forbiddenPackagePrefixes = ["docs/handoffs/", "project/" + "planning" + "/", "." + "edge-" + "agentic" + "/local/"];
106
+ const AUDIT_SCRIPT_EXEMPTIONS = new Set(["scripts/audit/public-surface.mjs", "scripts/audit/verify-pack.mjs"]);
107
+ const INTERNAL_HANDOFF_REFERENCE_EXEMPTIONS = new Set([...AUDIT_SCRIPT_EXEMPTIONS, "docs/reference/distribution.md"]);
86
108
  const forbiddenPackagedContentRules = [
87
109
  { name: "local evidence path", pattern: new RegExp(`\\.${"edge-" + "agentic"}/local`, "i") },
88
110
  { name: "local ODIN audit path", pattern: /\.odin\/local\//i },
89
111
  { name: "private planning path", pattern: new RegExp(`project/${"planning"}/`, "i") },
112
+ { name: "internal handoff path reference", pattern: /docs\/handoffs\//i, exemptFiles: INTERNAL_HANDOFF_REFERENCE_EXEMPTIONS },
90
113
  { name: "macOS home path", pattern: new RegExp(`/${"Users"}/[A-Za-z0-9._-]+/`) },
91
114
  { name: "Linux home path", pattern: /\/home\/[A-Za-z0-9._-]+\// },
92
- { name: "secret-looking assignment", pattern: /(api[_-]?key|secret|token|password)\s*[:=]\s*["'][^"']+["']/i }
115
+ { name: "secret-looking quoted assignment", pattern: /(api[_-]?key|secret|token|password)\s*[:=]\s*["'][^"']+["']/i },
116
+ { name: "secret-looking unquoted assignment", pattern: /(api[_-]?key|secret|token|password)\s*[:=]\s*[A-Za-z0-9._~+/=-]{16,}/i },
117
+ { name: "bearer token literal", pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{20,}/i },
118
+ { name: "URI credential literal", pattern: /[a-z][a-z0-9+.-]*:\/\/[^/\s:@]+:[^/\s:@]+@/i }
93
119
  ];
94
120
 
95
121
  function asPathSet(paths) {
@@ -104,6 +130,9 @@ export function validatePackageMetadata(packageJson) {
104
130
  if (!packageJson.license) errors.push("package.json missing license");
105
131
  if (!packageJson.engines?.node) errors.push("package.json missing engines.node");
106
132
  if (!Array.isArray(packageJson.files) || packageJson.files.length === 0) errors.push("package.json missing files allowlist");
133
+ if (packageJson.scripts?.prepublishOnly !== "pnpm run validate") {
134
+ errors.push("package.json prepublishOnly must run pnpm run validate");
135
+ }
107
136
  for (const file of [".claude-plugin", "docs", "plugins", "protocol", "templates", "AGENTS.md", "CLAUDE.md", "README.md", "LICENSE"]) {
108
137
  if (!packageJson.files?.includes(file)) errors.push(`package.json files allowlist missing ${file}`);
109
138
  }
@@ -121,6 +150,30 @@ export function validatePackageMetadata(packageJson) {
121
150
  return errors;
122
151
  }
123
152
 
153
+ function walkFiles(dir) {
154
+ if (!existsSync(dir)) return [];
155
+ return readdirSync(dir).flatMap((entry) => {
156
+ const path = join(dir, entry);
157
+ return statSync(path).isDirectory() ? walkFiles(path) : [path];
158
+ });
159
+ }
160
+
161
+ function expectedGeneratedDistFiles() {
162
+ const expected = new Set();
163
+ for (const file of walkFiles("src")) {
164
+ if (!file.endsWith(".ts") || file.endsWith(".d.ts")) continue;
165
+ const jsFile = file.replace(/^src\//, "dist/src/").replace(/\.ts$/, ".js");
166
+ expected.add(jsFile);
167
+ expected.add(`${jsFile}.map`);
168
+ expected.add(jsFile.replace(/\.js$/, ".d.ts"));
169
+ }
170
+ return expected;
171
+ }
172
+
173
+ function allowedGeneratedDistFiles() {
174
+ return new Set([...requiredPackageFiles.filter((file) => file.startsWith("dist/")), ...expectedGeneratedDistFiles()]);
175
+ }
176
+
124
177
  export function validatePackFileList(pathsInput) {
125
178
  const paths = asPathSet(pathsInput);
126
179
  const errors = [];
@@ -133,6 +186,10 @@ export function validatePackFileList(pathsInput) {
133
186
  const privatePaths = Array.from(paths).filter((file) => forbiddenPackagePrefixes.some((prefix) => file.startsWith(prefix)));
134
187
  if (privatePaths.length > 0) errors.push(`Package includes private local paths: ${privatePaths.join(", ")}`);
135
188
 
189
+ const allowed = new Set([...requiredPackageFiles, ...allowedGeneratedDistFiles()]);
190
+ const unexpected = Array.from(paths).filter((file) => !allowed.has(file));
191
+ if (unexpected.length > 0) errors.push(`Package includes unexpected files: ${unexpected.join(", ")}`);
192
+
136
193
  return errors;
137
194
  }
138
195
 
@@ -140,6 +197,7 @@ export function validatePackFileContents(fileTextByPath) {
140
197
  const findings = [];
141
198
  for (const [file, text] of Object.entries(fileTextByPath)) {
142
199
  for (const rule of forbiddenPackagedContentRules) {
200
+ if (rule.exemptFiles?.has(file)) continue;
143
201
  if (rule.pattern.test(text)) findings.push(`${file}: ${rule.name}`);
144
202
  }
145
203
  }
@@ -201,6 +259,56 @@ export function findStaleVersionReferences(fileTextByPath, currentVersion, minim
201
259
  return findings;
202
260
  }
203
261
 
262
+ export function findUnpinnedInstallReferences(fileTextByPath, currentVersion) {
263
+ const findings = [];
264
+ const packageName = "@bradheitmann/odin-sentinel";
265
+ const pinned = `${packageName}@${currentVersion}`;
266
+ const commandMarkers = [
267
+ "pnpm",
268
+ "npx",
269
+ "npm",
270
+ "claude mcp",
271
+ "--package",
272
+ "installurl",
273
+ "\"args\"",
274
+ "args =",
275
+ "command"
276
+ ];
277
+
278
+ for (const [file, text] of Object.entries(fileTextByPath)) {
279
+ if (AUDIT_SCRIPT_EXEMPTIONS.has(file)) continue;
280
+ const lines = text.split("\n");
281
+ for (const [index, line] of lines.entries()) {
282
+ if (!line.includes(packageName)) continue;
283
+ const windowText = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 3)).join(" ");
284
+ const lowerWindow = windowText.toLowerCase();
285
+ if (!commandMarkers.some((marker) => lowerWindow.includes(marker))) continue;
286
+ if (windowText.includes(pinned)) continue;
287
+ findings.push(`${file}:${index + 1}: install command must pin ${pinned}`);
288
+ }
289
+ }
290
+
291
+ return findings;
292
+ }
293
+
294
+ export function validateRuntimeVersionConstants(versionText, currentVersion, minimumCompatibleVersion = MINIMUM_COMPATIBLE_CHILD_MCP_VERSION, file = "src/protocol/version.ts") {
295
+ const errors = [];
296
+ const required = [
297
+ [`PROTOCOL_SCHEMA_VERSION`, currentVersion],
298
+ [`PUBLIC_LATEST_VERSION`, currentVersion],
299
+ [`MINIMUM_COMPATIBLE_MCP_VERSION`, minimumCompatibleVersion]
300
+ ];
301
+
302
+ for (const [constant, expected] of required) {
303
+ const pattern = new RegExp(`\\b${constant}\\s*=\\s*["']${expected.replaceAll(".", "\\.")}["']`);
304
+ if (!pattern.test(versionText)) {
305
+ errors.push(`${file}: ${constant} must be ${expected}`);
306
+ }
307
+ }
308
+
309
+ return errors;
310
+ }
311
+
204
312
  export function validatePublicProtocolSync({ scpText, bootstrapText, currentVersion, minimumCompatibleVersion = MINIMUM_COMPATIBLE_CHILD_MCP_VERSION }) {
205
313
  const errors = [];
206
314
  const requiredMarkers = [
@@ -244,9 +352,13 @@ export function validatePluginSync({ pluginManifestText, pluginSkillText, plugin
244
352
  } else {
245
353
  if (server.command !== "pnpm") errors.push("Claude plugin odin-sentinel server must use pnpm");
246
354
  const args = Array.isArray(server.args) ? server.args : [];
247
- for (const requiredArg of ["dlx", "--package", `@bradheitmann/odin-sentinel@${currentVersion}`, "odin-sentinel-mcp"]) {
355
+ const expectedArgs = ["dlx", "--package", `@bradheitmann/odin-sentinel@${currentVersion}`, "odin-sentinel-mcp"];
356
+ for (const requiredArg of expectedArgs) {
248
357
  if (!args.includes(requiredArg)) errors.push(`Claude plugin odin-sentinel args missing ${requiredArg}`);
249
358
  }
359
+ if (JSON.stringify(args) !== JSON.stringify(expectedArgs)) {
360
+ errors.push(`Claude plugin odin-sentinel args must exactly equal ${JSON.stringify(expectedArgs)}`);
361
+ }
250
362
  }
251
363
 
252
364
  for (const marker of [`SCP_PUBLIC_VERSION: ${currentVersion}`, `MIN_COMPATIBLE_CHILD_MCP: ${minimumCompatibleVersion}`]) {
@@ -331,6 +443,7 @@ function readPublicVersionFiles() {
331
443
  "docs/reference/client-compatibility.md",
332
444
  "docs/reference/distribution.md",
333
445
  "docs/reference/public-surface-audit.md",
446
+ "src/protocol/version.ts",
334
447
  ".claude-plugin/marketplace.json",
335
448
  "protocol/SCP.md",
336
449
  "protocol/bootstrap-skill.md",
@@ -363,6 +476,9 @@ export function runVerifyPack({ pack, packageJson, publicVersionFiles, costPriva
363
476
  ...validatePackFileContents(packFileTexts),
364
477
  ...validatePackagedProtocolVersions(packFileTexts, packageJson.version),
365
478
  ...findStaleVersionReferences(publicVersionFiles, packageJson.version),
479
+ ...findUnpinnedInstallReferences(packFileTexts, packageJson.version),
480
+ ...validateRuntimeVersionConstants(publicVersionFiles["src/protocol/version.ts"], packageJson.version),
481
+ ...validateRuntimeVersionConstants(packFileTexts["dist/src/protocol/version.js"] ?? "", packageJson.version, MINIMUM_COMPATIBLE_CHILD_MCP_VERSION, "dist/src/protocol/version.js"),
366
482
  ...validatePublicProtocolSync({
367
483
  scpText: publicVersionFiles["protocol/SCP.md"],
368
484
  bootstrapText: publicVersionFiles["protocol/bootstrap-skill.md"],