@t275005746/gse 0.1.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 (207) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +42 -0
  2. package/.github/ISSUE_TEMPLATE/change_request.yml +50 -0
  3. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +38 -0
  5. package/.github/workflows/validate-gse.yml +33 -0
  6. package/.gse/README.md +18 -0
  7. package/.gse/gse-development-protocol.md +50 -0
  8. package/.gse/project-profile.md +29 -0
  9. package/.gse/quality-gates.md +25 -0
  10. package/.gse/releases/public-channel-publication-pending.md +49 -0
  11. package/.gse/releases/public-ci-run-pending.md +53 -0
  12. package/.gse/releases/public-release-owner-required.md +65 -0
  13. package/.gse/releases/public-repository-settings-owner-required.md +59 -0
  14. package/.gse/releases/public-security-contact-owner-required.md +45 -0
  15. package/.gse/state.json +27 -0
  16. package/CHANGELOG.md +24 -0
  17. package/CONTRIBUTING.md +64 -0
  18. package/LICENSE +21 -0
  19. package/README.md +236 -0
  20. package/README.zh-CN.md +236 -0
  21. package/SECURITY.md +41 -0
  22. package/SKILL.md +148 -0
  23. package/SUPPORT.md +38 -0
  24. package/agents/openai.yaml +4 -0
  25. package/assets/marketplace/README.md +7 -0
  26. package/assets/marketplace/gse-listing.json +75 -0
  27. package/assets/templates/acceptance-execution-packet.md +73 -0
  28. package/assets/templates/adr.md +14 -0
  29. package/assets/templates/change-brief.md +16 -0
  30. package/assets/templates/design.md +18 -0
  31. package/assets/templates/dispatch-packet.md +104 -0
  32. package/assets/templates/evidence.md +14 -0
  33. package/assets/templates/execution-quality-pack.md +65 -0
  34. package/assets/templates/goal-map.md +21 -0
  35. package/assets/templates/host-adapter.md +48 -0
  36. package/assets/templates/host-ui-invocation-record.md +42 -0
  37. package/assets/templates/incident-review.md +60 -0
  38. package/assets/templates/public-channel-publication-record.md +49 -0
  39. package/assets/templates/public-ci-run-record.md +53 -0
  40. package/assets/templates/public-release-record.md +65 -0
  41. package/assets/templates/public-repository-settings-record.md +59 -0
  42. package/assets/templates/public-security-contact-record.md +45 -0
  43. package/assets/templates/release-trust-record.md +32 -0
  44. package/assets/templates/review.md +18 -0
  45. package/assets/templates/spec.md +16 -0
  46. package/assets/templates/target-adoption-evidence.md +29 -0
  47. package/assets/templates/tasks.md +17 -0
  48. package/assets/templates/update-release-acceptance-record.md +58 -0
  49. package/examples/README.md +22 -0
  50. package/examples/agent-runtime-host/.claude/gse-adapter.md +6 -0
  51. package/examples/agent-runtime-host/.codex/gse-adapter.md +7 -0
  52. package/examples/agent-runtime-host/.gse/goal-map.md +7 -0
  53. package/examples/agent-runtime-host/.gse/project-profile.md +27 -0
  54. package/examples/agent-runtime-host/.gse/tooling.md +5 -0
  55. package/examples/agent-runtime-host/.mcp.json +9 -0
  56. package/examples/agent-runtime-host/AGENTS.md +5 -0
  57. package/examples/agent-runtime-host/README.md +10 -0
  58. package/examples/agent-runtime-host/docs/model-routing.md +5 -0
  59. package/examples/cli-tool/.gse/project-profile.md +21 -0
  60. package/examples/cli-tool/AGENTS.md +5 -0
  61. package/examples/cli-tool/README.md +12 -0
  62. package/examples/cli-tool/package.json +21 -0
  63. package/examples/small-app/.env.example +2 -0
  64. package/examples/small-app/.github/workflows/ci.yml +8 -0
  65. package/examples/small-app/AGENTS.md +5 -0
  66. package/examples/small-app/README.md +12 -0
  67. package/examples/small-app/package.json +26 -0
  68. package/examples/small-app/playwright.config.ts +4 -0
  69. package/package.json +53 -0
  70. package/references/adoption-recipes.md +171 -0
  71. package/references/agent-roles.md +49 -0
  72. package/references/architecture-health.md +101 -0
  73. package/references/benchmark-audit.md +73 -0
  74. package/references/commands.md +295 -0
  75. package/references/community-channels.md +46 -0
  76. package/references/compatibility.md +70 -0
  77. package/references/design-basis.md +38 -0
  78. package/references/domain-model.md +129 -0
  79. package/references/domain-quality-gates.md +75 -0
  80. package/references/drift-audit.md +81 -0
  81. package/references/evidence-taxonomy.md +123 -0
  82. package/references/file-ownership.md +107 -0
  83. package/references/final-readiness.md +84 -0
  84. package/references/forward-test.md +133 -0
  85. package/references/goal-map.md +36 -0
  86. package/references/host-adapters.md +119 -0
  87. package/references/learning-system.md +35 -0
  88. package/references/marketplace-discovery.md +46 -0
  89. package/references/model-routing.md +103 -0
  90. package/references/open-source-defaults.md +39 -0
  91. package/references/operating-model.md +52 -0
  92. package/references/packaging.md +277 -0
  93. package/references/project-agent-workspace.md +89 -0
  94. package/references/project-bootstrap.md +122 -0
  95. package/references/project-profile.md +57 -0
  96. package/references/public-release.md +174 -0
  97. package/references/quality-gates.md +100 -0
  98. package/references/recovery.md +176 -0
  99. package/references/release-trust.md +43 -0
  100. package/references/release.md +126 -0
  101. package/references/review.md +141 -0
  102. package/references/router.md +90 -0
  103. package/references/spec-workflow.md +66 -0
  104. package/references/task-levels.md +81 -0
  105. package/references/tool-adapters.md +73 -0
  106. package/scripts/audit-acceptance-execution-packet.mjs +133 -0
  107. package/scripts/audit-adoption-recipes.mjs +99 -0
  108. package/scripts/audit-adoption.mjs +154 -0
  109. package/scripts/audit-change-lifecycle.mjs +77 -0
  110. package/scripts/audit-change-system.mjs +134 -0
  111. package/scripts/audit-ci-readiness.mjs +107 -0
  112. package/scripts/audit-close-gate.mjs +323 -0
  113. package/scripts/audit-command-adapters.mjs +91 -0
  114. package/scripts/audit-command-execution.mjs +210 -0
  115. package/scripts/audit-commands.mjs +117 -0
  116. package/scripts/audit-compatibility.mjs +149 -0
  117. package/scripts/audit-completion-readiness.mjs +292 -0
  118. package/scripts/audit-distribution.mjs +251 -0
  119. package/scripts/audit-domain-quality-gates.mjs +108 -0
  120. package/scripts/audit-evidence-placeholders.mjs +126 -0
  121. package/scripts/audit-final-acceptance-packet.mjs +148 -0
  122. package/scripts/audit-final-form-progress-report.mjs +161 -0
  123. package/scripts/audit-final-form-stale-copy.mjs +221 -0
  124. package/scripts/audit-final-readiness-promotion.mjs +255 -0
  125. package/scripts/audit-final-readiness.mjs +226 -0
  126. package/scripts/audit-fixtures.mjs +154 -0
  127. package/scripts/audit-fresh-session-readiness.mjs +177 -0
  128. package/scripts/audit-gse.mjs +275 -0
  129. package/scripts/audit-host-adapters.mjs +119 -0
  130. package/scripts/audit-host-runtime-evidence-handoff.mjs +159 -0
  131. package/scripts/audit-host-runtime-invocation-drill.mjs +240 -0
  132. package/scripts/audit-host-runtime-invocations.mjs +254 -0
  133. package/scripts/audit-host-ui-invocation.mjs +132 -0
  134. package/scripts/audit-learning-system.mjs +103 -0
  135. package/scripts/audit-local-final-form-completion.mjs +131 -0
  136. package/scripts/audit-marketplace-discovery.mjs +122 -0
  137. package/scripts/audit-npm-package-metadata.mjs +155 -0
  138. package/scripts/audit-npm-publish-dry-run.mjs +169 -0
  139. package/scripts/audit-npm-tarball-install.mjs +191 -0
  140. package/scripts/audit-open-source-defaults.mjs +92 -0
  141. package/scripts/audit-open-source-readiness.mjs +97 -0
  142. package/scripts/audit-owner-external-gate-kit.mjs +218 -0
  143. package/scripts/audit-project.mjs +323 -0
  144. package/scripts/audit-public-acceptance-command-dry-run-drill.mjs +203 -0
  145. package/scripts/audit-public-acceptance-handoff.mjs +142 -0
  146. package/scripts/audit-public-acceptance-readiness.mjs +224 -0
  147. package/scripts/audit-public-channel-publication.mjs +248 -0
  148. package/scripts/audit-public-ci-run.mjs +184 -0
  149. package/scripts/audit-public-collaboration-templates.mjs +98 -0
  150. package/scripts/audit-public-external-gate-probe.mjs +206 -0
  151. package/scripts/audit-public-release-checklist.mjs +133 -0
  152. package/scripts/audit-public-release-decision.mjs +201 -0
  153. package/scripts/audit-public-release-metadata.mjs +176 -0
  154. package/scripts/audit-public-repository-settings.mjs +237 -0
  155. package/scripts/audit-public-security-contact.mjs +171 -0
  156. package/scripts/audit-readme-docs.mjs +185 -0
  157. package/scripts/audit-recovery-readiness.mjs +98 -0
  158. package/scripts/audit-release-bundle.mjs +251 -0
  159. package/scripts/audit-release-owner-action-plan-drill.mjs +277 -0
  160. package/scripts/audit-release-owner-action-plan.mjs +164 -0
  161. package/scripts/audit-release-readiness.mjs +106 -0
  162. package/scripts/audit-release-status-manifest.mjs +165 -0
  163. package/scripts/audit-release-trust.mjs +62 -0
  164. package/scripts/audit-remote-distribution.mjs +266 -0
  165. package/scripts/audit-roadmap-consistency.mjs +235 -0
  166. package/scripts/audit-signing.mjs +147 -0
  167. package/scripts/audit-state-freshness.mjs +113 -0
  168. package/scripts/audit-target-adoption-evidence.mjs +117 -0
  169. package/scripts/audit-target-project.mjs +507 -0
  170. package/scripts/audit-update-release-acceptance.mjs +136 -0
  171. package/scripts/audit-v1-target-validation.mjs +269 -0
  172. package/scripts/audit-validation-profiles.mjs +125 -0
  173. package/scripts/close-change.mjs +116 -0
  174. package/scripts/discover-project-profile.mjs +307 -0
  175. package/scripts/forward-test-gse.mjs +146 -0
  176. package/scripts/generate-command-adapter.mjs +231 -0
  177. package/scripts/generate-final-acceptance-packet.mjs +181 -0
  178. package/scripts/generate-final-form-progress-report.mjs +205 -0
  179. package/scripts/generate-host-adapter.mjs +73 -0
  180. package/scripts/generate-host-runtime-evidence-handoff.mjs +206 -0
  181. package/scripts/generate-owner-external-gate-kit.mjs +295 -0
  182. package/scripts/generate-public-acceptance-handoff.mjs +168 -0
  183. package/scripts/generate-public-release-checklist.mjs +207 -0
  184. package/scripts/generate-release-bundle.mjs +505 -0
  185. package/scripts/generate-release-owner-action-plan.mjs +172 -0
  186. package/scripts/generate-release-status-manifest.mjs +200 -0
  187. package/scripts/generate-session-prompt.mjs +188 -0
  188. package/scripts/gse.mjs +67 -0
  189. package/scripts/init-change.mjs +265 -0
  190. package/scripts/init-project.mjs +785 -0
  191. package/scripts/install-gse.mjs +234 -0
  192. package/scripts/lib/evidence-placeholders.mjs +28 -0
  193. package/scripts/package-gse.mjs +174 -0
  194. package/scripts/probe-public-external-gates.mjs +167 -0
  195. package/scripts/record-host-invocation.mjs +151 -0
  196. package/scripts/record-learning.mjs +137 -0
  197. package/scripts/record-public-channel-publication.mjs +178 -0
  198. package/scripts/record-public-ci-run.mjs +180 -0
  199. package/scripts/record-public-release.mjs +175 -0
  200. package/scripts/record-public-repository-settings.mjs +209 -0
  201. package/scripts/record-public-security-contact.mjs +157 -0
  202. package/scripts/run-gse-command.mjs +538 -0
  203. package/scripts/run-validation-profile.mjs +146 -0
  204. package/scripts/sign-gse-package.mjs +83 -0
  205. package/scripts/update-project-state.mjs +223 -0
  206. package/scripts/validate-gse.mjs +1168 -0
  207. package/scripts/verify-gse-package.mjs +85 -0
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import crypto from 'node:crypto'
6
+ import { fileURLToPath } from 'node:url'
7
+
8
+ const args = process.argv.slice(2)
9
+
10
+ function readArg(name, fallback = null) {
11
+ const index = args.indexOf(name)
12
+ if (index === -1) return fallback
13
+ return args[index + 1] ?? fallback
14
+ }
15
+
16
+ const sourceArg = readArg('--source', null)
17
+ const sourceUrlArg = readArg('--source-url', null)
18
+ const manifestUrlArg = readArg('--manifest-url', null)
19
+ const publicKeyArg = readArg('--public-key', null)
20
+ const skipIntegrity = args.includes('--skip-integrity')
21
+ const skipSignature = args.includes('--skip-signature')
22
+ let materializedRemote = null
23
+ let source = path.resolve(sourceArg ?? path.join(import.meta.dirname, '..'))
24
+ const target = path.resolve(readArg('--target', ''))
25
+ const force = args.includes('--force')
26
+ const dryRun = args.includes('--dry-run')
27
+ const jsonOnly = args.includes('--json')
28
+
29
+ const defaultInclude = [
30
+ 'package.json',
31
+ 'SKILL.md',
32
+ 'README.md',
33
+ 'README.zh-CN.md',
34
+ 'agents',
35
+ 'assets',
36
+ 'examples',
37
+ 'references',
38
+ 'scripts',
39
+ '.gse',
40
+ ]
41
+
42
+ function joinUrl(base, relativePath) {
43
+ const cleanBase = base.endsWith('/') ? base : base + '/'
44
+ return new URL(relativePath.split('/').map(encodeURIComponent).join('/'), cleanBase).toString()
45
+ }
46
+
47
+ async function fetchText(url) {
48
+ const response = await fetch(url)
49
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`)
50
+ return await response.text()
51
+ }
52
+
53
+ async function fetchBytes(url) {
54
+ const response = await fetch(url)
55
+ if (!response.ok) throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`)
56
+ return Buffer.from(await response.arrayBuffer())
57
+ }
58
+
59
+ function sha256(filePath) {
60
+ return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex')
61
+ }
62
+
63
+ function fingerprint(pem) {
64
+ return crypto.createHash('sha256').update(pem).digest('hex')
65
+ }
66
+
67
+ function verifyPackageSignature(packageDir, publicKeyPath) {
68
+ const signaturePath = path.join(packageDir, 'gse-package-signature.json')
69
+ if (!publicKeyPath) return { status: 'not-requested' }
70
+ if (!fs.existsSync(publicKeyPath)) return { status: 'failed', error: 'public key does not exist', publicKey: publicKeyPath }
71
+ if (!fs.existsSync(signaturePath)) return { status: 'failed', error: 'signature file does not exist', signaturePath }
72
+ try {
73
+ const signatureRecord = JSON.parse(fs.readFileSync(signaturePath, 'utf8').replace(/^\uFEFF/, ''))
74
+ const publicKeyPem = fs.readFileSync(publicKeyPath, 'utf8')
75
+ const manifestPath = path.join(packageDir, 'gse-package-manifest.json')
76
+ const localManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8').replace(/^\uFEFF/, ''))
77
+ const payloadText = JSON.stringify(signatureRecord.payload)
78
+ const signatureOk = crypto.verify(null, Buffer.from(payloadText), publicKeyPem, Buffer.from(signatureRecord.signature, 'base64'))
79
+ const digestOk = signatureRecord.payload?.packageDigest === localManifest.integrity?.packageDigest
80
+ const fingerprintOk = signatureRecord.publicKeyFingerprint === fingerprint(publicKeyPem)
81
+ return signatureOk && digestOk && fingerprintOk
82
+ ? { status: 'verified', algorithm: signatureRecord.algorithm, publicKeyFingerprint: signatureRecord.publicKeyFingerprint }
83
+ : { status: 'failed', error: 'signature, digest, or public key fingerprint mismatch' }
84
+ } catch (error) {
85
+ return { status: 'failed', error: error instanceof Error ? error.message : String(error) }
86
+ }
87
+ }
88
+
89
+ async function resolveSource() {
90
+ if (!sourceUrlArg && !manifestUrlArg) return { source, sourceMode: 'path', remoteBaseUrl: null }
91
+ const urlText = sourceUrlArg ?? manifestUrlArg
92
+ const parsed = new URL(urlText)
93
+ if (parsed.protocol === 'file:') {
94
+ const resolved = fileURLToPath(parsed)
95
+ const stat = fs.existsSync(resolved) ? fs.statSync(resolved) : null
96
+ if (stat?.isFile()) return { source: path.dirname(resolved), sourceMode: 'file-url', remoteBaseUrl: null }
97
+ return { source: resolved, sourceMode: 'file-url', remoteBaseUrl: null }
98
+ }
99
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
100
+ throw new Error('Unsupported --source-url protocol. Expected file:, http:, or https:.')
101
+ }
102
+
103
+ const manifestUrl = manifestUrlArg ?? joinUrl(sourceUrlArg, 'gse-package-manifest.json')
104
+ const manifestText = await fetchText(manifestUrl)
105
+ const manifest = JSON.parse(manifestText.replace(/^\uFEFF/, ''))
106
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'gse-remote-install-'))
107
+ materializedRemote = tempRoot
108
+ fs.writeFileSync(path.join(tempRoot, 'gse-package-manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
109
+ const baseUrl = manifestUrl.slice(0, manifestUrl.lastIndexOf('/') + 1)
110
+ try {
111
+ const signatureText = await fetchText(joinUrl(baseUrl, 'gse-package-signature.json'))
112
+ fs.writeFileSync(path.join(tempRoot, 'gse-package-signature.json'), signatureText, 'utf8')
113
+ } catch {
114
+ // Signature is optional unless --public-key is supplied.
115
+ }
116
+ for (const relativePath of manifest.files ?? []) {
117
+ const fileUrl = joinUrl(baseUrl, relativePath)
118
+ const bytes = await fetchBytes(fileUrl)
119
+ const targetPath = path.join(tempRoot, relativePath)
120
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true })
121
+ fs.writeFileSync(targetPath, bytes)
122
+ }
123
+ return { source: tempRoot, sourceMode: 'http-url', remoteBaseUrl: baseUrl }
124
+ }
125
+
126
+ let sourceInfo
127
+ try {
128
+ sourceInfo = await resolveSource()
129
+ source = sourceInfo.source
130
+ } catch (error) {
131
+ const report = {
132
+ source: sourceArg,
133
+ sourceUrl: sourceUrlArg,
134
+ manifestUrl: manifestUrlArg,
135
+ target,
136
+ status: 'failed',
137
+ error: error instanceof Error ? error.message : String(error),
138
+ }
139
+ console.log(JSON.stringify(report, null, 2))
140
+ process.exit(1)
141
+ }
142
+
143
+ const manifestPath = path.join(source, 'gse-package-manifest.json')
144
+ const manifest = fs.existsSync(manifestPath)
145
+ ? JSON.parse(fs.readFileSync(manifestPath, 'utf8').replace(/^\uFEFF/, ''))
146
+ : null
147
+
148
+ function walk(itemPath, rootPath = source) {
149
+ const entries = []
150
+ if (!fs.existsSync(itemPath)) return entries
151
+ const stat = fs.statSync(itemPath)
152
+ if (stat.isFile()) {
153
+ entries.push(path.relative(rootPath, itemPath).replace(/\\/g, '/'))
154
+ return entries
155
+ }
156
+ for (const child of fs.readdirSync(itemPath, { withFileTypes: true })) {
157
+ const fullPath = path.join(itemPath, child.name)
158
+ if (child.name === 'node_modules' || child.name === '.git') continue
159
+ if (child.isDirectory()) entries.push(...walk(fullPath, rootPath))
160
+ else if (child.isFile()) entries.push(path.relative(rootPath, fullPath).replace(/\\/g, '/'))
161
+ }
162
+ return entries
163
+ }
164
+
165
+ function sourceFiles() {
166
+ if (manifest?.files?.length) return manifest.files
167
+ return defaultInclude.flatMap((item) => walk(path.join(source, item)))
168
+ }
169
+
170
+ function copy(relativePath) {
171
+ const from = path.join(source, relativePath)
172
+ const to = path.join(target, relativePath)
173
+ if (!fs.existsSync(from)) return { relativePath, status: 'missing-source' }
174
+ if (!skipIntegrity && manifest?.fileHashes?.[relativePath]) {
175
+ const actualHash = sha256(from)
176
+ if (actualHash !== manifest.fileHashes[relativePath]) {
177
+ return { relativePath, status: 'integrity-failed', expected: manifest.fileHashes[relativePath], actual: actualHash }
178
+ }
179
+ }
180
+ if (!force && fs.existsSync(to)) return { relativePath, status: 'skipped' }
181
+ if (!dryRun) {
182
+ fs.mkdirSync(path.dirname(to), { recursive: true })
183
+ fs.copyFileSync(from, to)
184
+ }
185
+ return { relativePath, status: dryRun ? 'would-write' : 'written' }
186
+ }
187
+
188
+ const files = sourceFiles()
189
+ const signatureStatus = skipSignature ? { status: 'skipped' } : verifyPackageSignature(source, publicKeyArg ? path.resolve(publicKeyArg) : null)
190
+ const report = {
191
+ source,
192
+ sourceUrl: sourceUrlArg,
193
+ manifestUrl: manifestUrlArg,
194
+ sourceMode: sourceInfo.sourceMode,
195
+ target,
196
+ dryRun,
197
+ force,
198
+ integrity: manifest?.integrity ? { algorithm: manifest.integrity.algorithm, packageDigest: manifest.integrity.packageDigest, skipped: skipIntegrity } : null,
199
+ signature: signatureStatus,
200
+ manifest: manifest ? { label: manifest.label, fileCount: manifest.fileCount } : null,
201
+ status: 'passed',
202
+ results: [],
203
+ summary: { written: 0, skipped: 0, missingSource: 0, integrityFailed: 0, total: files.length },
204
+ }
205
+
206
+ if (!target) {
207
+ report.status = 'failed'
208
+ report.error = 'Missing --target <install-skill-dir>.'
209
+ } else if (!fs.existsSync(source)) {
210
+ report.status = 'failed'
211
+ report.error = 'Source does not exist.'
212
+ } else if (signatureStatus.status === 'failed') {
213
+ report.status = 'failed'
214
+ report.error = 'Package signature verification failed.'
215
+ } else {
216
+ for (const file of files) report.results.push(copy(file))
217
+ report.summary.written = report.results.filter((item) => item.status === 'written' || item.status === 'would-write').length
218
+ report.summary.skipped = report.results.filter((item) => item.status === 'skipped').length
219
+ report.summary.missingSource = report.results.filter((item) => item.status === 'missing-source').length
220
+ report.summary.integrityFailed = report.results.filter((item) => item.status === 'integrity-failed').length
221
+ if (report.summary.missingSource > 0 || report.summary.integrityFailed > 0) report.status = 'failed'
222
+ }
223
+
224
+ if (jsonOnly) console.log(JSON.stringify(report, null, 2))
225
+ else {
226
+ console.log('GSE install status: ' + report.status)
227
+ console.log('Source: ' + report.source)
228
+ if (report.sourceUrl) console.log('Source URL: ' + report.sourceUrl)
229
+ console.log('Target: ' + report.target)
230
+ if (report.error) console.log('Error: ' + report.error)
231
+ console.log('Written: ' + report.summary.written + ', skipped: ' + report.summary.skipped + ', missing source: ' + report.summary.missingSource + ', integrity failed: ' + report.summary.integrityFailed)
232
+ }
233
+
234
+ if (report.status !== 'passed') process.exit(1)
@@ -0,0 +1,28 @@
1
+ export function isPlaceholderEvidence(value) {
2
+ const text = String(value ?? '').trim()
3
+ const lower = text.toLowerCase()
4
+ if (!text) return false
5
+ if (lower.includes('__placeholder__') || lower.includes('__') || /<[^>]+>/.test(lower)) return true
6
+ if (['owner', 'fixture', 'fixture-owner', 'todo', 'tbd', 'pending', 'example'].includes(lower)) return true
7
+ if (lower.endsWith('@example.com') || lower.endsWith('@localhost')) return true
8
+ try {
9
+ const url = new URL(text)
10
+ const host = url.hostname.toLowerCase()
11
+ if (host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0') return true
12
+ if (host === 'example.com' || host.endsWith('.example') || host.endsWith('.example.com')) return true
13
+ if (host.endsWith('.local')) return true
14
+ const firstPathSegment = url.pathname.split('/').filter(Boolean)[0]?.toLowerCase()
15
+ if ((host === 'github.com' || host === 'gitlab.com') && firstPathSegment === 'example') return true
16
+ } catch {
17
+ // Non-URL fields are checked by literal placeholder and email rules above.
18
+ }
19
+ return false
20
+ }
21
+
22
+ export function placeholderEvidenceError(label) {
23
+ return `${label} must be real public evidence, not a placeholder, fixture, local, or example value`
24
+ }
25
+
26
+ export function rejectPlaceholderEvidence(errors, value, label) {
27
+ if (isPlaceholderEvidence(value)) errors.push(placeholderEvidenceError(label))
28
+ }
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import crypto from 'node:crypto'
5
+
6
+ const args = process.argv.slice(2)
7
+
8
+ function readArg(name, fallback = null) {
9
+ const index = args.indexOf(name)
10
+ if (index === -1) return fallback
11
+ return args[index + 1] ?? fallback
12
+ }
13
+
14
+ const root = path.resolve(readArg('--root', path.join(import.meta.dirname, '..')))
15
+ const label = readArg('--label', 'gse-local-' + new Date().toISOString().slice(0, 10))
16
+ const out = path.resolve(readArg('--out', path.join(process.env.TEMP || process.env.TMP || root, label)))
17
+ const force = args.includes('--force')
18
+ const dryRun = args.includes('--dry-run')
19
+ const jsonOnly = args.includes('--json')
20
+
21
+ const include = [
22
+ 'package.json',
23
+ 'SKILL.md',
24
+ 'README.md',
25
+ 'README.zh-CN.md',
26
+ 'CHANGELOG.md',
27
+ 'CONTRIBUTING.md',
28
+ 'LICENSE',
29
+ 'SECURITY.md',
30
+ 'SUPPORT.md',
31
+ '.github',
32
+ 'agents',
33
+ 'assets',
34
+ 'examples',
35
+ 'references',
36
+ 'scripts',
37
+ '.gse',
38
+ ]
39
+
40
+ const excludeNames = new Set(['.git', 'node_modules', 'dist', '.DS_Store'])
41
+ const packageGseAllowlist = new Set([
42
+ '.gse/README.md',
43
+ '.gse/project-profile.md',
44
+ '.gse/quality-gates.md',
45
+ '.gse/gse-development-protocol.md',
46
+ '.gse/state.json',
47
+ '.gse/releases',
48
+ '.gse/releases/public-release-owner-required.md',
49
+ '.gse/releases/public-security-contact-owner-required.md',
50
+ ])
51
+ const excludeRelativePrefixes = [
52
+ '.gse/evidence/',
53
+ '.gse/benchmark-audits/',
54
+ '.gse/acceptance/',
55
+ '.gse/archive/',
56
+ '.gse/changes/',
57
+ '.gse/release-bundles/',
58
+ ]
59
+
60
+ function exists(relativePath) {
61
+ return fs.existsSync(path.join(root, relativePath))
62
+ }
63
+
64
+ function shouldExclude(relativePath) {
65
+ const normalized = relativePath.replace(/\\/g, '/')
66
+ if (normalized.startsWith('.gse/') && !packageGseAllowlist.has(normalized)) return true
67
+ if (excludeRelativePrefixes.some((prefix) => normalized.startsWith(prefix))) return true
68
+ return normalized.split('/').some((part) => excludeNames.has(part))
69
+ }
70
+
71
+ function walk(sourcePath, basePath = sourcePath) {
72
+ const entries = []
73
+ if (!fs.existsSync(sourcePath)) return entries
74
+ const stat = fs.statSync(sourcePath)
75
+ if (stat.isFile()) {
76
+ const relativePath = path.relative(root, sourcePath).replace(/\\/g, '/')
77
+ if (!shouldExclude(relativePath)) entries.push({ sourcePath, relativePath, bytes: stat.size })
78
+ return entries
79
+ }
80
+ for (const child of fs.readdirSync(sourcePath, { withFileTypes: true })) {
81
+ const fullPath = path.join(sourcePath, child.name)
82
+ const relativePath = path.relative(root, fullPath).replace(/\\/g, '/')
83
+ if (shouldExclude(relativePath)) continue
84
+ if (child.isDirectory()) entries.push(...walk(fullPath, basePath))
85
+ else if (child.isFile()) entries.push({ sourcePath: fullPath, relativePath, bytes: fs.statSync(fullPath).size })
86
+ }
87
+ return entries
88
+ }
89
+
90
+ function copyFile(entry) {
91
+ const targetPath = path.join(out, entry.relativePath)
92
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true })
93
+ fs.copyFileSync(entry.sourcePath, targetPath)
94
+ }
95
+
96
+ function sha256(filePath) {
97
+ return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex')
98
+ }
99
+
100
+ const missing = include.filter((item) => !exists(item))
101
+ const files = include.flatMap((item) => walk(path.join(root, item)))
102
+ const sortedFiles = files.map((item) => item.relativePath).sort()
103
+ const fileHashes = Object.fromEntries(
104
+ sortedFiles.map((relativePath) => [relativePath, sha256(path.join(root, relativePath))]),
105
+ )
106
+ const manifest = {
107
+ schemaVersion: 1,
108
+ packageName: 'gse',
109
+ label,
110
+ generatedAt: new Date().toISOString(),
111
+ fileCount: files.length,
112
+ totalBytes: files.reduce((sum, item) => sum + item.bytes, 0),
113
+ includes: include,
114
+ excludes: [...excludeNames, ...excludeRelativePrefixes, '.gse/* except portable package allowlist'],
115
+ packageGseAllowlist: [...packageGseAllowlist].sort(),
116
+ entrypoints: {
117
+ nodePackage: 'package.json',
118
+ skill: 'SKILL.md',
119
+ cli: 'scripts/gse.mjs',
120
+ validate: 'scripts/validate-gse.mjs',
121
+ npmPackageAudit: 'scripts/audit-npm-package-metadata.mjs',
122
+ validationProfile: 'scripts/run-validation-profile.mjs',
123
+ install: 'scripts/install-gse.mjs',
124
+ commands: 'references/commands.md',
125
+ },
126
+ integrity: {
127
+ algorithm: 'sha256',
128
+ },
129
+ files: sortedFiles,
130
+ fileHashes,
131
+ }
132
+ manifest.integrity.packageDigest = crypto
133
+ .createHash('sha256')
134
+ .update(JSON.stringify({ files: manifest.files, fileHashes: manifest.fileHashes }))
135
+ .digest('hex')
136
+
137
+ const report = {
138
+ root,
139
+ out,
140
+ label,
141
+ dryRun,
142
+ status: missing.length === 0 ? 'ready' : 'failed',
143
+ missing,
144
+ fileCount: files.length,
145
+ totalBytes: manifest.totalBytes,
146
+ manifestPath: path.join(out, 'gse-package-manifest.json'),
147
+ }
148
+
149
+ if (missing.length === 0 && !dryRun) {
150
+ if (fs.existsSync(out)) {
151
+ if (!force) {
152
+ report.status = 'exists'
153
+ report.recommendation = 'Use --force or choose another --out directory.'
154
+ } else {
155
+ fs.rmSync(out, { recursive: true, force: true })
156
+ }
157
+ }
158
+ if (report.status === 'ready') {
159
+ fs.mkdirSync(out, { recursive: true })
160
+ for (const file of files) copyFile(file)
161
+ fs.writeFileSync(path.join(out, 'gse-package-manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf8')
162
+ report.status = 'written'
163
+ }
164
+ }
165
+
166
+ if (jsonOnly) console.log(JSON.stringify(report, null, 2))
167
+ else {
168
+ console.log('GSE package status: ' + report.status)
169
+ console.log('Output: ' + report.out)
170
+ console.log('Files: ' + report.fileCount)
171
+ if (report.missing.length) console.log('Missing: ' + report.missing.join(', '))
172
+ }
173
+
174
+ if (report.status === 'failed' || report.status === 'exists') process.exit(1)
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { isPlaceholderEvidence } from './lib/evidence-placeholders.mjs'
5
+
6
+ const args = process.argv.slice(2)
7
+
8
+ function readArg(name, fallback = '') {
9
+ const index = args.indexOf(name)
10
+ if (index === -1) return fallback
11
+ return args[index + 1] ?? fallback
12
+ }
13
+
14
+ function hasArg(name) {
15
+ return args.includes(name)
16
+ }
17
+
18
+ const root = path.resolve(readArg('--root', path.join(import.meta.dirname, '..')))
19
+ const jsonOnly = hasArg('--json')
20
+ const allowLocalFixture = hasArg('--allow-local-fixture')
21
+ const timeoutMs = Number(readArg('--timeout-ms', '5000'))
22
+
23
+ const inputs = [
24
+ { area: 'Public repository settings', kind: 'url', flag: '--public-repo-url', value: readArg('--public-repo-url') },
25
+ { area: 'Public security contact', kind: 'url', flag: '--security-contact-url', value: readArg('--security-contact-url') },
26
+ { area: 'Public CI run', kind: 'url', flag: '--public-ci-run-url', value: readArg('--public-ci-run-url') },
27
+ { area: 'Public registry publication', kind: 'url', flag: '--registry-package-url', value: readArg('--registry-package-url') },
28
+ { area: 'Marketplace approval', kind: 'url', flag: '--marketplace-url', value: readArg('--marketplace-url') },
29
+ { area: 'Native slash command', kind: 'evidence', flag: '--native-host-evidence', value: readArg('--native-host-evidence') },
30
+ { area: 'Other host runtime invocation', kind: 'evidence', flag: '--other-host-evidence', value: readArg('--other-host-evidence') },
31
+ ].filter((item) => String(item.value ?? '').trim())
32
+
33
+ function isLocalUrl(value) {
34
+ try {
35
+ const url = new URL(value)
36
+ const host = url.hostname.toLowerCase()
37
+ return host === 'localhost' || host === '127.0.0.1' || host === '0.0.0.0' || host.endsWith('.local')
38
+ } catch {
39
+ return false
40
+ }
41
+ }
42
+
43
+ function isHttpUrl(value) {
44
+ try {
45
+ const url = new URL(value)
46
+ return url.protocol === 'http:' || url.protocol === 'https:'
47
+ } catch {
48
+ return false
49
+ }
50
+ }
51
+
52
+ async function probeUrl(value) {
53
+ const controller = new AbortController()
54
+ const timeout = setTimeout(() => controller.abort(), Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 5000)
55
+ try {
56
+ let response = await fetch(value, { method: 'HEAD', redirect: 'follow', signal: controller.signal })
57
+ if (response.status === 405 || response.status === 403) {
58
+ response = await fetch(value, { method: 'GET', redirect: 'follow', signal: controller.signal })
59
+ }
60
+ return {
61
+ reachable: response.status >= 200 && response.status < 500,
62
+ statusCode: response.status,
63
+ finalUrl: response.url,
64
+ }
65
+ } catch (error) {
66
+ return {
67
+ reachable: false,
68
+ error: error?.name === 'AbortError' ? 'timeout' : String(error?.message ?? error),
69
+ }
70
+ } finally {
71
+ clearTimeout(timeout)
72
+ }
73
+ }
74
+
75
+ function probeEvidence(value) {
76
+ if (isHttpUrl(value)) return null
77
+ const fullPath = path.isAbsolute(value) ? value : path.resolve(root, value)
78
+ return {
79
+ reachable: fs.existsSync(fullPath),
80
+ path: fullPath,
81
+ type: fs.existsSync(fullPath) ? 'file' : 'missing',
82
+ }
83
+ }
84
+
85
+ const probes = []
86
+ for (const item of inputs) {
87
+ const placeholder = isPlaceholderEvidence(item.value)
88
+ const local = isLocalUrl(item.value)
89
+ const errors = []
90
+ if (placeholder && !(allowLocalFixture && local)) errors.push(`${item.flag} must be real public evidence, not a placeholder, fixture, local, or example value`)
91
+ if (item.kind === 'url' && !isHttpUrl(item.value)) errors.push(`${item.flag} must be an http or https URL`)
92
+ if (item.kind === 'url' && local && !allowLocalFixture) errors.push(`${item.flag} must not be local evidence`)
93
+ if (item.kind === 'evidence' && !isHttpUrl(item.value) && !fs.existsSync(path.isAbsolute(item.value) ? item.value : path.resolve(root, item.value))) {
94
+ errors.push(`${item.flag} must be an http/https URL or an existing evidence file`)
95
+ }
96
+ probes.push({ ...item, placeholder, local, errors })
97
+ }
98
+
99
+ for (const probe of probes) {
100
+ if (probe.errors.length) continue
101
+ if (probe.kind === 'url' || isHttpUrl(probe.value)) {
102
+ probe.network = await probeUrl(probe.value)
103
+ if (!probe.network.reachable) probe.errors.push(`${probe.flag} is not reachable`)
104
+ } else {
105
+ probe.file = probeEvidence(probe.value)
106
+ if (!probe.file.reachable) probe.errors.push(`${probe.flag} evidence file is not reachable`)
107
+ }
108
+ }
109
+
110
+ const failed = probes.filter((item) => item.errors.length > 0).length
111
+ const ready = probes.length > 0 && failed === 0
112
+ const report = {
113
+ root,
114
+ generatedAt: new Date().toISOString(),
115
+ status: probes.length === 0 ? 'waiting-for-input' : ready ? 'ready' : 'failed',
116
+ summary: {
117
+ checked: probes.length,
118
+ ready: probes.length - failed,
119
+ failed,
120
+ allowLocalFixture,
121
+ },
122
+ probes: probes.map((item) => ({
123
+ area: item.area,
124
+ flag: item.flag,
125
+ kind: item.kind,
126
+ value: item.value,
127
+ placeholder: item.placeholder,
128
+ local: item.local,
129
+ status: item.errors.length === 0 ? 'ready' : 'failed',
130
+ network: item.network,
131
+ file: item.file,
132
+ errors: item.errors,
133
+ })),
134
+ limits: [
135
+ 'This probe checks supplied owner/external evidence locations before record creation.',
136
+ 'It does not publish GSE, configure repository settings, approve marketplace listings, or mark final readiness accepted.',
137
+ 'Accepted gates still require the matching record script and final readiness promotion.',
138
+ ],
139
+ }
140
+
141
+ function renderMarkdown(data) {
142
+ const lines = []
143
+ lines.push('# GSE Public External Gate Probe')
144
+ lines.push('')
145
+ lines.push('Generated: ' + data.generatedAt)
146
+ lines.push('Status: ' + data.status)
147
+ lines.push('')
148
+ lines.push('## Probes')
149
+ lines.push('')
150
+ if (data.probes.length === 0) lines.push('- No evidence inputs supplied.')
151
+ for (const probe of data.probes) {
152
+ lines.push('- ' + probe.area + ': ' + probe.status + ' (' + probe.flag + ')')
153
+ if (probe.network) lines.push(' - HTTP status: ' + probe.network.statusCode)
154
+ if (probe.file) lines.push(' - File: ' + probe.file.path)
155
+ for (const error of probe.errors) lines.push(' - Error: ' + error)
156
+ }
157
+ lines.push('')
158
+ lines.push('## Limits')
159
+ lines.push('')
160
+ for (const item of data.limits) lines.push('- ' + item)
161
+ return lines.join('\n') + '\n'
162
+ }
163
+
164
+ if (jsonOnly) console.log(JSON.stringify(report, null, 2))
165
+ else console.log(renderMarkdown(report))
166
+
167
+ if (report.status === 'failed') process.exit(1)