@t275005746/gse 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +42 -42
- package/.github/ISSUE_TEMPLATE/change_request.yml +50 -50
- package/.github/ISSUE_TEMPLATE/config.yml +5 -5
- package/.github/PULL_REQUEST_TEMPLATE.md +38 -38
- package/.github/workflows/validate-gse.yml +33 -33
- package/.gse/README.md +18 -18
- package/.gse/gse-development-protocol.md +50 -50
- package/.gse/project-profile.md +29 -29
- package/.gse/quality-gates.md +25 -25
- package/.gse/releases/public-registry-publication-npm.md +49 -0
- package/.gse/releases/public-release-owner-required.md +65 -65
- package/.gse/releases/public-security-contact-owner-required.md +45 -45
- package/.gse/state.json +3 -4
- package/CHANGELOG.md +24 -24
- package/CONTRIBUTING.md +64 -64
- package/LICENSE +21 -21
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/SECURITY.md +41 -41
- package/SUPPORT.md +38 -38
- package/assets/marketplace/README.md +7 -7
- package/assets/marketplace/gse-listing.json +75 -75
- package/assets/templates/acceptance-execution-packet.md +73 -73
- package/assets/templates/adr.md +14 -14
- package/assets/templates/change-brief.md +16 -16
- package/assets/templates/design.md +18 -18
- package/assets/templates/dispatch-packet.md +104 -104
- package/assets/templates/evidence.md +14 -14
- package/assets/templates/execution-quality-pack.md +65 -65
- package/assets/templates/goal-map.md +21 -21
- package/assets/templates/host-adapter.md +48 -48
- package/assets/templates/host-ui-invocation-record.md +42 -42
- package/assets/templates/incident-review.md +60 -60
- package/assets/templates/public-channel-publication-record.md +49 -49
- package/assets/templates/public-ci-run-record.md +53 -53
- package/assets/templates/public-release-record.md +65 -65
- package/assets/templates/public-repository-settings-record.md +59 -59
- package/assets/templates/public-security-contact-record.md +45 -45
- package/assets/templates/release-trust-record.md +32 -32
- package/assets/templates/review.md +18 -18
- package/assets/templates/spec.md +16 -16
- package/assets/templates/target-adoption-evidence.md +29 -29
- package/assets/templates/tasks.md +17 -17
- package/assets/templates/update-release-acceptance-record.md +58 -58
- package/examples/README.md +22 -22
- package/examples/agent-runtime-host/.claude/gse-adapter.md +6 -6
- package/examples/agent-runtime-host/.codex/gse-adapter.md +7 -7
- package/examples/agent-runtime-host/.gse/goal-map.md +7 -7
- package/examples/agent-runtime-host/.gse/project-profile.md +27 -27
- package/examples/agent-runtime-host/.gse/tooling.md +5 -5
- package/examples/agent-runtime-host/.mcp.json +9 -9
- package/examples/agent-runtime-host/AGENTS.md +5 -5
- package/examples/agent-runtime-host/README.md +10 -10
- package/examples/agent-runtime-host/docs/model-routing.md +5 -5
- package/examples/cli-tool/.gse/project-profile.md +21 -21
- package/examples/cli-tool/AGENTS.md +5 -5
- package/examples/cli-tool/README.md +12 -12
- package/examples/cli-tool/package.json +21 -21
- package/examples/small-app/.env.example +2 -2
- package/examples/small-app/.github/workflows/ci.yml +8 -8
- package/examples/small-app/AGENTS.md +5 -5
- package/examples/small-app/README.md +12 -12
- package/examples/small-app/package.json +26 -26
- package/examples/small-app/playwright.config.ts +4 -4
- package/package.json +53 -53
- package/references/adoption-recipes.md +171 -171
- package/references/agent-roles.md +49 -49
- package/references/architecture-health.md +101 -101
- package/references/benchmark-audit.md +73 -73
- package/references/community-channels.md +46 -46
- package/references/compatibility.md +70 -70
- package/references/design-basis.md +38 -38
- package/references/domain-model.md +129 -129
- package/references/domain-quality-gates.md +75 -75
- package/references/drift-audit.md +81 -81
- package/references/evidence-taxonomy.md +123 -123
- package/references/file-ownership.md +107 -107
- package/references/final-readiness.md +84 -84
- package/references/forward-test.md +133 -133
- package/references/goal-map.md +36 -36
- package/references/host-adapters.md +119 -119
- package/references/learning-system.md +35 -35
- package/references/marketplace-discovery.md +46 -46
- package/references/model-routing.md +103 -103
- package/references/open-source-defaults.md +39 -39
- package/references/operating-model.md +52 -52
- package/references/packaging.md +277 -277
- package/references/project-agent-workspace.md +89 -89
- package/references/project-bootstrap.md +122 -122
- package/references/project-profile.md +57 -57
- package/references/public-release.md +174 -174
- package/references/quality-gates.md +100 -100
- package/references/recovery.md +176 -176
- package/references/release-trust.md +43 -43
- package/references/release.md +126 -126
- package/references/review.md +141 -141
- package/references/router.md +90 -90
- package/references/spec-workflow.md +66 -66
- package/references/task-levels.md +81 -81
- package/references/tool-adapters.md +73 -73
- package/scripts/audit-acceptance-execution-packet.mjs +133 -133
- package/scripts/audit-adoption-recipes.mjs +99 -99
- package/scripts/audit-change-lifecycle.mjs +77 -77
- package/scripts/audit-change-system.mjs +134 -134
- package/scripts/audit-ci-readiness.mjs +107 -107
- package/scripts/audit-close-gate.mjs +323 -323
- package/scripts/audit-command-adapters.mjs +91 -91
- package/scripts/audit-command-execution.mjs +210 -210
- package/scripts/audit-compatibility.mjs +149 -149
- package/scripts/audit-completion-readiness.mjs +292 -292
- package/scripts/audit-distribution.mjs +251 -251
- package/scripts/audit-domain-quality-gates.mjs +108 -108
- package/scripts/audit-evidence-placeholders.mjs +126 -126
- package/scripts/audit-final-readiness-promotion.mjs +255 -255
- package/scripts/audit-final-readiness.mjs +226 -226
- package/scripts/audit-fixtures.mjs +154 -154
- package/scripts/audit-fresh-session-readiness.mjs +177 -177
- package/scripts/audit-host-runtime-evidence-handoff.mjs +159 -159
- package/scripts/audit-host-runtime-invocation-drill.mjs +240 -240
- package/scripts/audit-host-runtime-invocations.mjs +254 -254
- package/scripts/audit-host-ui-invocation.mjs +132 -132
- package/scripts/audit-marketplace-discovery.mjs +122 -122
- package/scripts/audit-npm-package-metadata.mjs +155 -155
- package/scripts/audit-npm-publish-dry-run.mjs +191 -169
- package/scripts/audit-npm-tarball-install.mjs +191 -191
- package/scripts/audit-open-source-defaults.mjs +92 -92
- package/scripts/audit-open-source-readiness.mjs +97 -97
- package/scripts/audit-project.mjs +138 -138
- package/scripts/audit-public-acceptance-command-dry-run-drill.mjs +203 -203
- package/scripts/audit-public-acceptance-readiness.mjs +224 -224
- package/scripts/audit-public-channel-publication.mjs +248 -248
- package/scripts/audit-public-ci-run.mjs +184 -184
- package/scripts/audit-public-collaboration-templates.mjs +98 -98
- package/scripts/audit-public-external-gate-probe.mjs +206 -206
- package/scripts/audit-public-release-decision.mjs +201 -201
- package/scripts/audit-public-release-metadata.mjs +176 -176
- package/scripts/audit-public-repository-settings.mjs +237 -237
- package/scripts/audit-public-security-contact.mjs +171 -171
- package/scripts/audit-readme-docs.mjs +6 -4
- package/scripts/audit-recovery-readiness.mjs +98 -98
- package/scripts/audit-release-readiness.mjs +106 -106
- package/scripts/audit-release-trust.mjs +62 -62
- package/scripts/audit-remote-distribution.mjs +266 -266
- package/scripts/audit-roadmap-consistency.mjs +235 -235
- package/scripts/audit-signing.mjs +147 -147
- package/scripts/audit-state-freshness.mjs +14 -9
- package/scripts/audit-target-adoption-evidence.mjs +117 -117
- package/scripts/audit-target-project.mjs +507 -507
- package/scripts/audit-update-release-acceptance.mjs +136 -136
- package/scripts/audit-v1-target-validation.mjs +269 -269
- package/scripts/audit-validation-profiles.mjs +125 -125
- package/scripts/close-change.mjs +116 -116
- package/scripts/discover-project-profile.mjs +307 -307
- package/scripts/generate-command-adapter.mjs +231 -231
- package/scripts/generate-final-acceptance-packet.mjs +181 -181
- package/scripts/generate-final-form-progress-report.mjs +205 -205
- package/scripts/generate-host-runtime-evidence-handoff.mjs +206 -206
- package/scripts/generate-owner-external-gate-kit.mjs +295 -295
- package/scripts/generate-public-acceptance-handoff.mjs +168 -168
- package/scripts/generate-public-release-checklist.mjs +207 -207
- package/scripts/generate-release-bundle.mjs +505 -505
- package/scripts/generate-release-owner-action-plan.mjs +172 -172
- package/scripts/generate-release-status-manifest.mjs +200 -200
- package/scripts/generate-session-prompt.mjs +188 -188
- package/scripts/gse.mjs +67 -67
- package/scripts/init-change.mjs +265 -265
- package/scripts/init-project.mjs +785 -785
- package/scripts/install-gse.mjs +234 -234
- package/scripts/lib/evidence-placeholders.mjs +28 -28
- package/scripts/package-gse.mjs +174 -174
- package/scripts/probe-public-external-gates.mjs +167 -167
- package/scripts/record-host-invocation.mjs +151 -151
- package/scripts/record-public-channel-publication.mjs +178 -178
- package/scripts/record-public-ci-run.mjs +180 -180
- package/scripts/record-public-release.mjs +175 -175
- package/scripts/record-public-repository-settings.mjs +209 -209
- package/scripts/record-public-security-contact.mjs +157 -157
- package/scripts/sign-gse-package.mjs +83 -83
- package/scripts/update-project-state.mjs +223 -223
- package/scripts/verify-gse-package.mjs +85 -85
|
@@ -1,323 +1,323 @@
|
|
|
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 { spawnSync } from 'node:child_process'
|
|
6
|
-
|
|
7
|
-
const args = process.argv.slice(2)
|
|
8
|
-
|
|
9
|
-
function readArg(name, fallback = null) {
|
|
10
|
-
const index = args.indexOf(name)
|
|
11
|
-
if (index === -1) return fallback
|
|
12
|
-
return args[index + 1] ?? fallback
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const jsonOnly = args.includes('--json')
|
|
16
|
-
const selfTest = args.includes('--self-test') || !args.includes('--target')
|
|
17
|
-
const targetArg = readArg('--target')
|
|
18
|
-
|
|
19
|
-
function readText(filePath) {
|
|
20
|
-
return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '') : ''
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function readJson(filePath) {
|
|
24
|
-
if (!fs.existsSync(filePath)) return { exists: false, ok: false, data: null, error: 'missing' }
|
|
25
|
-
try {
|
|
26
|
-
return { exists: true, ok: true, data: JSON.parse(readText(filePath)), error: '' }
|
|
27
|
-
} catch (error) {
|
|
28
|
-
return { exists: true, ok: false, data: null, error: error.message }
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function readJsonl(filePath) {
|
|
33
|
-
if (!fs.existsSync(filePath)) return { exists: false, ok: false, records: [], error: 'missing' }
|
|
34
|
-
const lines = readText(filePath)
|
|
35
|
-
.split(/\r?\n/)
|
|
36
|
-
.map((line) => line.trim())
|
|
37
|
-
.filter(Boolean)
|
|
38
|
-
const records = []
|
|
39
|
-
for (const [index, line] of lines.entries()) {
|
|
40
|
-
try {
|
|
41
|
-
records.push(JSON.parse(line))
|
|
42
|
-
} catch (error) {
|
|
43
|
-
return { exists: true, ok: false, records, error: `line ${index + 1}: ${error.message}` }
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return { exists: true, ok: true, records, error: '' }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function exists(target, relativePath) {
|
|
50
|
-
return fs.existsSync(path.join(target, relativePath))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function runGit(target, commandArgs) {
|
|
54
|
-
const result = spawnSync('git', commandArgs, {
|
|
55
|
-
cwd: target,
|
|
56
|
-
encoding: 'utf8',
|
|
57
|
-
windowsHide: true,
|
|
58
|
-
})
|
|
59
|
-
return {
|
|
60
|
-
status: result.status ?? 1,
|
|
61
|
-
stdout: (result.stdout ?? '').trim(),
|
|
62
|
-
stderr: (result.stderr ?? '').trim(),
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function statusFrom(ok, warn = false) {
|
|
67
|
-
if (ok) return 'passed'
|
|
68
|
-
if (warn) return 'warning'
|
|
69
|
-
return 'failed'
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function check(id, label, status, evidence, recommendation = '') {
|
|
73
|
-
return { id, label, status, evidence, recommendation }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function createFixture() {
|
|
77
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gse-close-gate-'))
|
|
78
|
-
fs.mkdirSync(path.join(dir, '.gse', 'evidence'), { recursive: true })
|
|
79
|
-
fs.writeFileSync(path.join(dir, '.gse', 'README.md'), '# GSE\n', 'utf8')
|
|
80
|
-
fs.writeFileSync(path.join(dir, '.gse', 'project-profile.md'), '# Project Profile\n', 'utf8')
|
|
81
|
-
fs.writeFileSync(path.join(dir, '.gse', 'goal-map.md'), '# Goal Map\n\nNext action: archive slice.\n', 'utf8')
|
|
82
|
-
fs.writeFileSync(path.join(dir, '.gse', 'quality-gates.md'), '# Quality Gates\n\n## Universal\n\n- Evidence required.\n', 'utf8')
|
|
83
|
-
fs.writeFileSync(path.join(dir, '.gse', 'evidence', '2026-07-06.md'), '# Evidence\n\nEvidence status: verified.\n', 'utf8')
|
|
84
|
-
fs.writeFileSync(
|
|
85
|
-
path.join(dir, '.gse', 'state.json'),
|
|
86
|
-
JSON.stringify(
|
|
87
|
-
{
|
|
88
|
-
schemaVersion: 1,
|
|
89
|
-
projectName: 'fixture-product',
|
|
90
|
-
mode: 'standard',
|
|
91
|
-
canonicalPlan: '',
|
|
92
|
-
phase: 'verify',
|
|
93
|
-
currentSlice: {
|
|
94
|
-
id: 'fixture-close',
|
|
95
|
-
outcome: 'Fixture close gate.',
|
|
96
|
-
status: 'verified',
|
|
97
|
-
nextAction: 'Archive slice.',
|
|
98
|
-
},
|
|
99
|
-
toolStatuses: {
|
|
100
|
-
browser: 'unknown',
|
|
101
|
-
lsp: 'unknown',
|
|
102
|
-
mcp: 'unknown',
|
|
103
|
-
subagents: 'unknown',
|
|
104
|
-
ci: 'unknown',
|
|
105
|
-
},
|
|
106
|
-
lastEvidence: '.gse/evidence/2026-07-06.md',
|
|
107
|
-
residualRisks: ['Fixture residual risk.'],
|
|
108
|
-
},
|
|
109
|
-
null,
|
|
110
|
-
2,
|
|
111
|
-
) + '\n',
|
|
112
|
-
'utf8',
|
|
113
|
-
)
|
|
114
|
-
fs.writeFileSync(
|
|
115
|
-
path.join(dir, '.gse', 'evidence', 'index.jsonl'),
|
|
116
|
-
JSON.stringify({
|
|
117
|
-
date: '2026-07-06',
|
|
118
|
-
recordType: 'slice',
|
|
119
|
-
status: 'verified',
|
|
120
|
-
summary: 'Fixture close gate evidence.',
|
|
121
|
-
evidenceFile: '.gse/evidence/2026-07-06.md',
|
|
122
|
-
commands: ['node scripts/audit-close-gate.mjs --self-test'],
|
|
123
|
-
nextAction: 'Archive slice.',
|
|
124
|
-
}) + '\n',
|
|
125
|
-
'utf8',
|
|
126
|
-
)
|
|
127
|
-
runGit(dir, ['init'])
|
|
128
|
-
runGit(dir, ['config', 'user.email', 'gse-fixture@example.local'])
|
|
129
|
-
runGit(dir, ['config', 'user.name', 'GSE Fixture'])
|
|
130
|
-
runGit(dir, ['add', '.'])
|
|
131
|
-
runGit(dir, ['commit', '-m', 'fixture'])
|
|
132
|
-
return dir
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function auditCloseGate(target) {
|
|
136
|
-
const resolvedTarget = path.resolve(target)
|
|
137
|
-
const gseDir = path.join(resolvedTarget, '.gse')
|
|
138
|
-
const state = readJson(path.join(gseDir, 'state.json'))
|
|
139
|
-
const evidenceIndex = readJsonl(path.join(gseDir, 'evidence', 'index.jsonl'))
|
|
140
|
-
const checks = []
|
|
141
|
-
|
|
142
|
-
checks.push(
|
|
143
|
-
check(
|
|
144
|
-
'CG01',
|
|
145
|
-
'.gse directory exists',
|
|
146
|
-
statusFrom(exists(resolvedTarget, '.gse')),
|
|
147
|
-
exists(resolvedTarget, '.gse') ? '.gse exists' : '.gse missing',
|
|
148
|
-
exists(resolvedTarget, '.gse') ? '' : 'Initialize or adopt GSE before closing a slice.',
|
|
149
|
-
),
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
const stateValid =
|
|
153
|
-
state.ok &&
|
|
154
|
-
state.data?.schemaVersion === 1 &&
|
|
155
|
-
typeof state.data?.phase === 'string' &&
|
|
156
|
-
typeof state.data?.currentSlice?.status === 'string' &&
|
|
157
|
-
typeof state.data?.currentSlice?.nextAction === 'string'
|
|
158
|
-
checks.push(
|
|
159
|
-
check(
|
|
160
|
-
'CG02',
|
|
161
|
-
'state.json is valid',
|
|
162
|
-
statusFrom(stateValid),
|
|
163
|
-
state.exists ? state.ok ? `phase:${state.data.phase}, status:${state.data.currentSlice?.status}` : 'invalid ' + state.error : 'missing',
|
|
164
|
-
stateValid ? '' : 'Repair .gse/state.json before closing the slice.',
|
|
165
|
-
),
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
const indexValid =
|
|
169
|
-
evidenceIndex.ok &&
|
|
170
|
-
evidenceIndex.records.length > 0 &&
|
|
171
|
-
evidenceIndex.records.every(
|
|
172
|
-
(record) =>
|
|
173
|
-
typeof record.date === 'string' &&
|
|
174
|
-
typeof record.recordType === 'string' &&
|
|
175
|
-
typeof record.status === 'string' &&
|
|
176
|
-
typeof record.summary === 'string' &&
|
|
177
|
-
typeof record.evidenceFile === 'string' &&
|
|
178
|
-
Array.isArray(record.commands) &&
|
|
179
|
-
typeof record.nextAction === 'string',
|
|
180
|
-
)
|
|
181
|
-
checks.push(
|
|
182
|
-
check(
|
|
183
|
-
'CG03',
|
|
184
|
-
'evidence index is valid',
|
|
185
|
-
statusFrom(indexValid),
|
|
186
|
-
evidenceIndex.exists ? evidenceIndex.ok ? `${evidenceIndex.records.length} record(s)` : 'invalid ' + evidenceIndex.error : 'missing',
|
|
187
|
-
indexValid ? '' : 'Record at least one evidence index entry before closing.',
|
|
188
|
-
),
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
const closeableState = ['verified', 'accepted'].includes(state.data?.currentSlice?.status)
|
|
192
|
-
checks.push(
|
|
193
|
-
check(
|
|
194
|
-
'CG04',
|
|
195
|
-
'current slice status is closeable',
|
|
196
|
-
statusFrom(closeableState),
|
|
197
|
-
`currentSlice.status:${state.data?.currentSlice?.status ?? 'unknown'}`,
|
|
198
|
-
closeableState ? '' : 'Set currentSlice.status to verified or accepted only after focused evidence exists.',
|
|
199
|
-
),
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
const closeableEvidenceRecords = evidenceIndex.records.filter((record) => ['verified', 'accepted'].includes(record.status))
|
|
203
|
-
const evidenceFilesExist =
|
|
204
|
-
closeableEvidenceRecords.length > 0 &&
|
|
205
|
-
closeableEvidenceRecords.every((record) => exists(resolvedTarget, record.evidenceFile))
|
|
206
|
-
checks.push(
|
|
207
|
-
check(
|
|
208
|
-
'CG05',
|
|
209
|
-
'verified or accepted evidence record exists',
|
|
210
|
-
statusFrom(evidenceFilesExist),
|
|
211
|
-
closeableEvidenceRecords.length
|
|
212
|
-
? closeableEvidenceRecords.map((record) => `${record.status}:${record.evidenceFile}:${exists(resolvedTarget, record.evidenceFile) ? 'exists' : 'missing'}`).join('; ')
|
|
213
|
-
: 'no verified/accepted evidence index record',
|
|
214
|
-
evidenceFilesExist ? '' : 'Add verified evidence and ensure the referenced evidence file exists.',
|
|
215
|
-
),
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
const requiredDocs = ['.gse/README.md', '.gse/project-profile.md', '.gse/goal-map.md', '.gse/quality-gates.md']
|
|
219
|
-
const missingDocs = requiredDocs.filter((relativePath) => !exists(resolvedTarget, relativePath))
|
|
220
|
-
checks.push(
|
|
221
|
-
check(
|
|
222
|
-
'CG06',
|
|
223
|
-
'required workflow docs exist',
|
|
224
|
-
statusFrom(missingDocs.length === 0),
|
|
225
|
-
missingDocs.length ? 'missing: ' + missingDocs.join(', ') : `${requiredDocs.length}/${requiredDocs.length} present`,
|
|
226
|
-
missingDocs.length ? 'Restore required workflow docs before closing.' : '',
|
|
227
|
-
),
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
const residualRisksOk = Array.isArray(state.data?.residualRisks)
|
|
231
|
-
checks.push(
|
|
232
|
-
check(
|
|
233
|
-
'CG07',
|
|
234
|
-
'residual risks are explicit',
|
|
235
|
-
statusFrom(residualRisksOk),
|
|
236
|
-
residualRisksOk ? `${state.data.residualRisks.length} residual risk(s)` : 'missing residualRisks array',
|
|
237
|
-
residualRisksOk ? '' : 'Use an empty array if there is no known residual risk.',
|
|
238
|
-
),
|
|
239
|
-
)
|
|
240
|
-
|
|
241
|
-
const gitRoot = runGit(resolvedTarget, ['rev-parse', '--show-toplevel'])
|
|
242
|
-
const isGitRepo = gitRoot.status === 0
|
|
243
|
-
const gseStatus = isGitRepo ? runGit(resolvedTarget, ['status', '--short', '.gse']) : { status: 1, stdout: '', stderr: 'not a git repository' }
|
|
244
|
-
checks.push(
|
|
245
|
-
check(
|
|
246
|
-
'CG08',
|
|
247
|
-
'.gse git state is known',
|
|
248
|
-
isGitRepo ? (gseStatus.stdout ? 'warning' : 'passed') : 'warning',
|
|
249
|
-
isGitRepo ? gseStatus.stdout || 'clean' : 'not a git repository',
|
|
250
|
-
isGitRepo && gseStatus.stdout ? 'Review and intentionally stage/commit or document project-local GSE changes.' : '',
|
|
251
|
-
),
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
const failed = checks.filter((item) => item.status === 'failed').length
|
|
255
|
-
const warnings = checks.filter((item) => item.status === 'warning').length
|
|
256
|
-
const passed = checks.filter((item) => item.status === 'passed').length
|
|
257
|
-
|
|
258
|
-
return {
|
|
259
|
-
target: resolvedTarget,
|
|
260
|
-
generatedAt: new Date().toISOString(),
|
|
261
|
-
summary: {
|
|
262
|
-
status: failed > 0 ? 'not-ready' : warnings > 0 ? 'ready-with-warnings' : 'ready',
|
|
263
|
-
passed,
|
|
264
|
-
warnings,
|
|
265
|
-
failed,
|
|
266
|
-
total: checks.length,
|
|
267
|
-
},
|
|
268
|
-
workflows: {
|
|
269
|
-
closeGate: failed > 0 ? 'not-ready' : warnings > 0 ? 'ready-with-warnings' : 'ready',
|
|
270
|
-
},
|
|
271
|
-
state: {
|
|
272
|
-
exists: state.exists,
|
|
273
|
-
valid: stateValid,
|
|
274
|
-
phase: state.data?.phase ?? null,
|
|
275
|
-
currentSliceStatus: state.data?.currentSlice?.status ?? null,
|
|
276
|
-
nextAction: state.data?.currentSlice?.nextAction ?? null,
|
|
277
|
-
},
|
|
278
|
-
evidenceIndex: {
|
|
279
|
-
exists: evidenceIndex.exists,
|
|
280
|
-
valid: indexValid,
|
|
281
|
-
records: evidenceIndex.records.length,
|
|
282
|
-
closeableRecords: closeableEvidenceRecords.length,
|
|
283
|
-
},
|
|
284
|
-
checks,
|
|
285
|
-
limits: [
|
|
286
|
-
'Close gate is diagnostic. It reports ready/not-ready and does not modify files.',
|
|
287
|
-
'A ready close gate does not replace user, reviewer, release, or owner acceptance when the project requires it.',
|
|
288
|
-
'Project tests, browser smokes, CI, MCP, LSP, subagents, and release checks must still be run according to project quality gates.',
|
|
289
|
-
],
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function renderMarkdown(report) {
|
|
294
|
-
const lines = []
|
|
295
|
-
lines.push('# GSE Close Gate')
|
|
296
|
-
lines.push('')
|
|
297
|
-
lines.push('Generated: ' + report.generatedAt)
|
|
298
|
-
lines.push('Target: ' + report.target)
|
|
299
|
-
lines.push('')
|
|
300
|
-
lines.push('## Summary')
|
|
301
|
-
lines.push('')
|
|
302
|
-
lines.push('- Status: ' + report.summary.status)
|
|
303
|
-
lines.push('- Checks: ' + report.summary.passed + ' passed, ' + report.summary.warnings + ' warnings, ' + report.summary.failed + ' failed, ' + report.summary.total + ' total')
|
|
304
|
-
lines.push('')
|
|
305
|
-
lines.push('## Checks')
|
|
306
|
-
lines.push('')
|
|
307
|
-
for (const item of report.checks) {
|
|
308
|
-
const marker = item.status === 'passed' ? '[x]' : item.status === 'warning' ? '[!]' : '[ ]'
|
|
309
|
-
lines.push('- ' + marker + ' ' + item.id + ' ' + item.label + ': ' + item.evidence)
|
|
310
|
-
if (item.recommendation) lines.push(' - Recommendation: ' + item.recommendation)
|
|
311
|
-
}
|
|
312
|
-
lines.push('')
|
|
313
|
-
lines.push('## Limits')
|
|
314
|
-
lines.push('')
|
|
315
|
-
for (const item of report.limits) lines.push('- ' + item)
|
|
316
|
-
return lines.join('\n') + '\n'
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const target = selfTest ? createFixture() : targetArg
|
|
320
|
-
const report = auditCloseGate(target)
|
|
321
|
-
|
|
322
|
-
if (jsonOnly) console.log(JSON.stringify(report, null, 2))
|
|
323
|
-
else console.log(renderMarkdown(report))
|
|
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 { spawnSync } from 'node:child_process'
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
|
+
|
|
9
|
+
function readArg(name, fallback = null) {
|
|
10
|
+
const index = args.indexOf(name)
|
|
11
|
+
if (index === -1) return fallback
|
|
12
|
+
return args[index + 1] ?? fallback
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const jsonOnly = args.includes('--json')
|
|
16
|
+
const selfTest = args.includes('--self-test') || !args.includes('--target')
|
|
17
|
+
const targetArg = readArg('--target')
|
|
18
|
+
|
|
19
|
+
function readText(filePath) {
|
|
20
|
+
return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '') : ''
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readJson(filePath) {
|
|
24
|
+
if (!fs.existsSync(filePath)) return { exists: false, ok: false, data: null, error: 'missing' }
|
|
25
|
+
try {
|
|
26
|
+
return { exists: true, ok: true, data: JSON.parse(readText(filePath)), error: '' }
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return { exists: true, ok: false, data: null, error: error.message }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readJsonl(filePath) {
|
|
33
|
+
if (!fs.existsSync(filePath)) return { exists: false, ok: false, records: [], error: 'missing' }
|
|
34
|
+
const lines = readText(filePath)
|
|
35
|
+
.split(/\r?\n/)
|
|
36
|
+
.map((line) => line.trim())
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
const records = []
|
|
39
|
+
for (const [index, line] of lines.entries()) {
|
|
40
|
+
try {
|
|
41
|
+
records.push(JSON.parse(line))
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return { exists: true, ok: false, records, error: `line ${index + 1}: ${error.message}` }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { exists: true, ok: true, records, error: '' }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function exists(target, relativePath) {
|
|
50
|
+
return fs.existsSync(path.join(target, relativePath))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function runGit(target, commandArgs) {
|
|
54
|
+
const result = spawnSync('git', commandArgs, {
|
|
55
|
+
cwd: target,
|
|
56
|
+
encoding: 'utf8',
|
|
57
|
+
windowsHide: true,
|
|
58
|
+
})
|
|
59
|
+
return {
|
|
60
|
+
status: result.status ?? 1,
|
|
61
|
+
stdout: (result.stdout ?? '').trim(),
|
|
62
|
+
stderr: (result.stderr ?? '').trim(),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function statusFrom(ok, warn = false) {
|
|
67
|
+
if (ok) return 'passed'
|
|
68
|
+
if (warn) return 'warning'
|
|
69
|
+
return 'failed'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function check(id, label, status, evidence, recommendation = '') {
|
|
73
|
+
return { id, label, status, evidence, recommendation }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createFixture() {
|
|
77
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gse-close-gate-'))
|
|
78
|
+
fs.mkdirSync(path.join(dir, '.gse', 'evidence'), { recursive: true })
|
|
79
|
+
fs.writeFileSync(path.join(dir, '.gse', 'README.md'), '# GSE\n', 'utf8')
|
|
80
|
+
fs.writeFileSync(path.join(dir, '.gse', 'project-profile.md'), '# Project Profile\n', 'utf8')
|
|
81
|
+
fs.writeFileSync(path.join(dir, '.gse', 'goal-map.md'), '# Goal Map\n\nNext action: archive slice.\n', 'utf8')
|
|
82
|
+
fs.writeFileSync(path.join(dir, '.gse', 'quality-gates.md'), '# Quality Gates\n\n## Universal\n\n- Evidence required.\n', 'utf8')
|
|
83
|
+
fs.writeFileSync(path.join(dir, '.gse', 'evidence', '2026-07-06.md'), '# Evidence\n\nEvidence status: verified.\n', 'utf8')
|
|
84
|
+
fs.writeFileSync(
|
|
85
|
+
path.join(dir, '.gse', 'state.json'),
|
|
86
|
+
JSON.stringify(
|
|
87
|
+
{
|
|
88
|
+
schemaVersion: 1,
|
|
89
|
+
projectName: 'fixture-product',
|
|
90
|
+
mode: 'standard',
|
|
91
|
+
canonicalPlan: '',
|
|
92
|
+
phase: 'verify',
|
|
93
|
+
currentSlice: {
|
|
94
|
+
id: 'fixture-close',
|
|
95
|
+
outcome: 'Fixture close gate.',
|
|
96
|
+
status: 'verified',
|
|
97
|
+
nextAction: 'Archive slice.',
|
|
98
|
+
},
|
|
99
|
+
toolStatuses: {
|
|
100
|
+
browser: 'unknown',
|
|
101
|
+
lsp: 'unknown',
|
|
102
|
+
mcp: 'unknown',
|
|
103
|
+
subagents: 'unknown',
|
|
104
|
+
ci: 'unknown',
|
|
105
|
+
},
|
|
106
|
+
lastEvidence: '.gse/evidence/2026-07-06.md',
|
|
107
|
+
residualRisks: ['Fixture residual risk.'],
|
|
108
|
+
},
|
|
109
|
+
null,
|
|
110
|
+
2,
|
|
111
|
+
) + '\n',
|
|
112
|
+
'utf8',
|
|
113
|
+
)
|
|
114
|
+
fs.writeFileSync(
|
|
115
|
+
path.join(dir, '.gse', 'evidence', 'index.jsonl'),
|
|
116
|
+
JSON.stringify({
|
|
117
|
+
date: '2026-07-06',
|
|
118
|
+
recordType: 'slice',
|
|
119
|
+
status: 'verified',
|
|
120
|
+
summary: 'Fixture close gate evidence.',
|
|
121
|
+
evidenceFile: '.gse/evidence/2026-07-06.md',
|
|
122
|
+
commands: ['node scripts/audit-close-gate.mjs --self-test'],
|
|
123
|
+
nextAction: 'Archive slice.',
|
|
124
|
+
}) + '\n',
|
|
125
|
+
'utf8',
|
|
126
|
+
)
|
|
127
|
+
runGit(dir, ['init'])
|
|
128
|
+
runGit(dir, ['config', 'user.email', 'gse-fixture@example.local'])
|
|
129
|
+
runGit(dir, ['config', 'user.name', 'GSE Fixture'])
|
|
130
|
+
runGit(dir, ['add', '.'])
|
|
131
|
+
runGit(dir, ['commit', '-m', 'fixture'])
|
|
132
|
+
return dir
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function auditCloseGate(target) {
|
|
136
|
+
const resolvedTarget = path.resolve(target)
|
|
137
|
+
const gseDir = path.join(resolvedTarget, '.gse')
|
|
138
|
+
const state = readJson(path.join(gseDir, 'state.json'))
|
|
139
|
+
const evidenceIndex = readJsonl(path.join(gseDir, 'evidence', 'index.jsonl'))
|
|
140
|
+
const checks = []
|
|
141
|
+
|
|
142
|
+
checks.push(
|
|
143
|
+
check(
|
|
144
|
+
'CG01',
|
|
145
|
+
'.gse directory exists',
|
|
146
|
+
statusFrom(exists(resolvedTarget, '.gse')),
|
|
147
|
+
exists(resolvedTarget, '.gse') ? '.gse exists' : '.gse missing',
|
|
148
|
+
exists(resolvedTarget, '.gse') ? '' : 'Initialize or adopt GSE before closing a slice.',
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
const stateValid =
|
|
153
|
+
state.ok &&
|
|
154
|
+
state.data?.schemaVersion === 1 &&
|
|
155
|
+
typeof state.data?.phase === 'string' &&
|
|
156
|
+
typeof state.data?.currentSlice?.status === 'string' &&
|
|
157
|
+
typeof state.data?.currentSlice?.nextAction === 'string'
|
|
158
|
+
checks.push(
|
|
159
|
+
check(
|
|
160
|
+
'CG02',
|
|
161
|
+
'state.json is valid',
|
|
162
|
+
statusFrom(stateValid),
|
|
163
|
+
state.exists ? state.ok ? `phase:${state.data.phase}, status:${state.data.currentSlice?.status}` : 'invalid ' + state.error : 'missing',
|
|
164
|
+
stateValid ? '' : 'Repair .gse/state.json before closing the slice.',
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const indexValid =
|
|
169
|
+
evidenceIndex.ok &&
|
|
170
|
+
evidenceIndex.records.length > 0 &&
|
|
171
|
+
evidenceIndex.records.every(
|
|
172
|
+
(record) =>
|
|
173
|
+
typeof record.date === 'string' &&
|
|
174
|
+
typeof record.recordType === 'string' &&
|
|
175
|
+
typeof record.status === 'string' &&
|
|
176
|
+
typeof record.summary === 'string' &&
|
|
177
|
+
typeof record.evidenceFile === 'string' &&
|
|
178
|
+
Array.isArray(record.commands) &&
|
|
179
|
+
typeof record.nextAction === 'string',
|
|
180
|
+
)
|
|
181
|
+
checks.push(
|
|
182
|
+
check(
|
|
183
|
+
'CG03',
|
|
184
|
+
'evidence index is valid',
|
|
185
|
+
statusFrom(indexValid),
|
|
186
|
+
evidenceIndex.exists ? evidenceIndex.ok ? `${evidenceIndex.records.length} record(s)` : 'invalid ' + evidenceIndex.error : 'missing',
|
|
187
|
+
indexValid ? '' : 'Record at least one evidence index entry before closing.',
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
const closeableState = ['verified', 'accepted'].includes(state.data?.currentSlice?.status)
|
|
192
|
+
checks.push(
|
|
193
|
+
check(
|
|
194
|
+
'CG04',
|
|
195
|
+
'current slice status is closeable',
|
|
196
|
+
statusFrom(closeableState),
|
|
197
|
+
`currentSlice.status:${state.data?.currentSlice?.status ?? 'unknown'}`,
|
|
198
|
+
closeableState ? '' : 'Set currentSlice.status to verified or accepted only after focused evidence exists.',
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
const closeableEvidenceRecords = evidenceIndex.records.filter((record) => ['verified', 'accepted'].includes(record.status))
|
|
203
|
+
const evidenceFilesExist =
|
|
204
|
+
closeableEvidenceRecords.length > 0 &&
|
|
205
|
+
closeableEvidenceRecords.every((record) => exists(resolvedTarget, record.evidenceFile))
|
|
206
|
+
checks.push(
|
|
207
|
+
check(
|
|
208
|
+
'CG05',
|
|
209
|
+
'verified or accepted evidence record exists',
|
|
210
|
+
statusFrom(evidenceFilesExist),
|
|
211
|
+
closeableEvidenceRecords.length
|
|
212
|
+
? closeableEvidenceRecords.map((record) => `${record.status}:${record.evidenceFile}:${exists(resolvedTarget, record.evidenceFile) ? 'exists' : 'missing'}`).join('; ')
|
|
213
|
+
: 'no verified/accepted evidence index record',
|
|
214
|
+
evidenceFilesExist ? '' : 'Add verified evidence and ensure the referenced evidence file exists.',
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
const requiredDocs = ['.gse/README.md', '.gse/project-profile.md', '.gse/goal-map.md', '.gse/quality-gates.md']
|
|
219
|
+
const missingDocs = requiredDocs.filter((relativePath) => !exists(resolvedTarget, relativePath))
|
|
220
|
+
checks.push(
|
|
221
|
+
check(
|
|
222
|
+
'CG06',
|
|
223
|
+
'required workflow docs exist',
|
|
224
|
+
statusFrom(missingDocs.length === 0),
|
|
225
|
+
missingDocs.length ? 'missing: ' + missingDocs.join(', ') : `${requiredDocs.length}/${requiredDocs.length} present`,
|
|
226
|
+
missingDocs.length ? 'Restore required workflow docs before closing.' : '',
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
const residualRisksOk = Array.isArray(state.data?.residualRisks)
|
|
231
|
+
checks.push(
|
|
232
|
+
check(
|
|
233
|
+
'CG07',
|
|
234
|
+
'residual risks are explicit',
|
|
235
|
+
statusFrom(residualRisksOk),
|
|
236
|
+
residualRisksOk ? `${state.data.residualRisks.length} residual risk(s)` : 'missing residualRisks array',
|
|
237
|
+
residualRisksOk ? '' : 'Use an empty array if there is no known residual risk.',
|
|
238
|
+
),
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
const gitRoot = runGit(resolvedTarget, ['rev-parse', '--show-toplevel'])
|
|
242
|
+
const isGitRepo = gitRoot.status === 0
|
|
243
|
+
const gseStatus = isGitRepo ? runGit(resolvedTarget, ['status', '--short', '.gse']) : { status: 1, stdout: '', stderr: 'not a git repository' }
|
|
244
|
+
checks.push(
|
|
245
|
+
check(
|
|
246
|
+
'CG08',
|
|
247
|
+
'.gse git state is known',
|
|
248
|
+
isGitRepo ? (gseStatus.stdout ? 'warning' : 'passed') : 'warning',
|
|
249
|
+
isGitRepo ? gseStatus.stdout || 'clean' : 'not a git repository',
|
|
250
|
+
isGitRepo && gseStatus.stdout ? 'Review and intentionally stage/commit or document project-local GSE changes.' : '',
|
|
251
|
+
),
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
const failed = checks.filter((item) => item.status === 'failed').length
|
|
255
|
+
const warnings = checks.filter((item) => item.status === 'warning').length
|
|
256
|
+
const passed = checks.filter((item) => item.status === 'passed').length
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
target: resolvedTarget,
|
|
260
|
+
generatedAt: new Date().toISOString(),
|
|
261
|
+
summary: {
|
|
262
|
+
status: failed > 0 ? 'not-ready' : warnings > 0 ? 'ready-with-warnings' : 'ready',
|
|
263
|
+
passed,
|
|
264
|
+
warnings,
|
|
265
|
+
failed,
|
|
266
|
+
total: checks.length,
|
|
267
|
+
},
|
|
268
|
+
workflows: {
|
|
269
|
+
closeGate: failed > 0 ? 'not-ready' : warnings > 0 ? 'ready-with-warnings' : 'ready',
|
|
270
|
+
},
|
|
271
|
+
state: {
|
|
272
|
+
exists: state.exists,
|
|
273
|
+
valid: stateValid,
|
|
274
|
+
phase: state.data?.phase ?? null,
|
|
275
|
+
currentSliceStatus: state.data?.currentSlice?.status ?? null,
|
|
276
|
+
nextAction: state.data?.currentSlice?.nextAction ?? null,
|
|
277
|
+
},
|
|
278
|
+
evidenceIndex: {
|
|
279
|
+
exists: evidenceIndex.exists,
|
|
280
|
+
valid: indexValid,
|
|
281
|
+
records: evidenceIndex.records.length,
|
|
282
|
+
closeableRecords: closeableEvidenceRecords.length,
|
|
283
|
+
},
|
|
284
|
+
checks,
|
|
285
|
+
limits: [
|
|
286
|
+
'Close gate is diagnostic. It reports ready/not-ready and does not modify files.',
|
|
287
|
+
'A ready close gate does not replace user, reviewer, release, or owner acceptance when the project requires it.',
|
|
288
|
+
'Project tests, browser smokes, CI, MCP, LSP, subagents, and release checks must still be run according to project quality gates.',
|
|
289
|
+
],
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function renderMarkdown(report) {
|
|
294
|
+
const lines = []
|
|
295
|
+
lines.push('# GSE Close Gate')
|
|
296
|
+
lines.push('')
|
|
297
|
+
lines.push('Generated: ' + report.generatedAt)
|
|
298
|
+
lines.push('Target: ' + report.target)
|
|
299
|
+
lines.push('')
|
|
300
|
+
lines.push('## Summary')
|
|
301
|
+
lines.push('')
|
|
302
|
+
lines.push('- Status: ' + report.summary.status)
|
|
303
|
+
lines.push('- Checks: ' + report.summary.passed + ' passed, ' + report.summary.warnings + ' warnings, ' + report.summary.failed + ' failed, ' + report.summary.total + ' total')
|
|
304
|
+
lines.push('')
|
|
305
|
+
lines.push('## Checks')
|
|
306
|
+
lines.push('')
|
|
307
|
+
for (const item of report.checks) {
|
|
308
|
+
const marker = item.status === 'passed' ? '[x]' : item.status === 'warning' ? '[!]' : '[ ]'
|
|
309
|
+
lines.push('- ' + marker + ' ' + item.id + ' ' + item.label + ': ' + item.evidence)
|
|
310
|
+
if (item.recommendation) lines.push(' - Recommendation: ' + item.recommendation)
|
|
311
|
+
}
|
|
312
|
+
lines.push('')
|
|
313
|
+
lines.push('## Limits')
|
|
314
|
+
lines.push('')
|
|
315
|
+
for (const item of report.limits) lines.push('- ' + item)
|
|
316
|
+
return lines.join('\n') + '\n'
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const target = selfTest ? createFixture() : targetArg
|
|
320
|
+
const report = auditCloseGate(target)
|
|
321
|
+
|
|
322
|
+
if (jsonOnly) console.log(JSON.stringify(report, null, 2))
|
|
323
|
+
else console.log(renderMarkdown(report))
|