@t275005746/gse 0.1.0 → 0.1.2
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 +147 -27
- package/CHANGELOG.md +31 -15
- package/CONTRIBUTING.md +64 -64
- package/LICENSE +21 -21
- package/README.md +14 -7
- package/README.zh-CN.md +14 -7
- package/SECURITY.md +41 -41
- package/SKILL.md +32 -12
- 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 +100 -92
- package/assets/templates/evidence.md +15 -9
- 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/role-fallback-packet.md +49 -0
- 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 +52 -44
- package/references/adoption-recipes.md +171 -171
- package/references/agent-roles.md +42 -21
- package/references/architecture-health.md +101 -101
- package/references/benchmark-audit.md +73 -73
- package/references/commands.md +74 -45
- 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 -80
- package/references/evidence-taxonomy.md +145 -108
- package/references/file-ownership.md +97 -93
- package/references/final-form-roadmap.md +201 -0
- 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 +129 -101
- package/references/learning-system.md +42 -26
- package/references/maintenance-cadence.md +51 -0
- 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-guards.md +52 -0
- package/references/project-profile.md +57 -57
- package/references/public-release.md +174 -174
- package/references/quality-gates.md +96 -89
- 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/role-dispatch-fallback.md +54 -0
- 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 +80 -70
- 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-hardening.mjs +188 -0
- package/scripts/audit-close-gate.mjs +448 -262
- package/scripts/audit-command-adapters.mjs +91 -91
- package/scripts/audit-command-execution.mjs +212 -199
- package/scripts/audit-commands.mjs +7 -5
- package/scripts/audit-compatibility.mjs +149 -149
- package/scripts/audit-completion-plan-drill.mjs +230 -0
- package/scripts/audit-completion-readiness.mjs +290 -287
- package/scripts/audit-continue-preflight.mjs +234 -0
- package/scripts/audit-distribution.mjs +251 -251
- package/scripts/audit-domain-quality-gates.mjs +108 -108
- package/scripts/audit-evidence-levels.mjs +217 -0
- package/scripts/audit-evidence-placeholders.mjs +126 -126
- package/scripts/audit-evidence-review-queue.mjs +206 -0
- package/scripts/audit-final-acceptance-packet.mjs +9 -9
- package/scripts/audit-final-form-progress-report.mjs +19 -18
- package/scripts/audit-final-form-roadmap.mjs +157 -0
- package/scripts/audit-final-form-stale-copy.mjs +2 -2
- 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-capabilities.mjs +237 -0
- 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-installed-sync.mjs +203 -0
- package/scripts/audit-learning-drift.mjs +292 -0
- package/scripts/audit-learning-promotion.mjs +351 -0
- package/scripts/audit-learning-system.mjs +24 -14
- package/scripts/audit-local-final-form-completion.mjs +11 -10
- package/scripts/audit-maintenance-cadence.mjs +139 -0
- package/scripts/audit-maintenance-snapshot.mjs +181 -0
- package/scripts/audit-marketplace-discovery.mjs +122 -122
- package/scripts/audit-npm-package-metadata.mjs +160 -154
- package/scripts/audit-npm-publish-dry-run.mjs +194 -166
- package/scripts/audit-npm-tarball-install.mjs +195 -189
- package/scripts/audit-open-source-defaults.mjs +92 -92
- package/scripts/audit-open-source-readiness.mjs +97 -97
- package/scripts/audit-owner-external-gate-kit.mjs +12 -11
- package/scripts/audit-project-guards.mjs +189 -0
- package/scripts/audit-project.mjs +144 -141
- package/scripts/audit-public-acceptance-command-dry-run-drill.mjs +203 -203
- package/scripts/audit-public-acceptance-handoff.mjs +10 -10
- package/scripts/audit-public-acceptance-readiness.mjs +222 -222
- 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-checklist.mjs +17 -15
- 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-bundle.mjs +13 -11
- package/scripts/audit-release-owner-action-plan-drill.mjs +5 -4
- package/scripts/audit-release-owner-action-plan.mjs +10 -10
- package/scripts/audit-release-readiness.mjs +106 -106
- package/scripts/audit-release-status-manifest.mjs +4 -4
- package/scripts/audit-release-trust.mjs +62 -62
- package/scripts/audit-remote-distribution.mjs +266 -266
- package/scripts/audit-roadmap-consistency.mjs +234 -230
- package/scripts/audit-role-dispatch-fallback.mjs +212 -0
- package/scripts/audit-session-sync.mjs +127 -0
- package/scripts/audit-signing.mjs +147 -147
- package/scripts/audit-state-freshness.mjs +20 -14
- package/scripts/audit-state-repair.mjs +360 -0
- package/scripts/audit-target-adoption-evidence.mjs +117 -117
- package/scripts/audit-target-hardening-drills.mjs +267 -0
- package/scripts/audit-target-project.mjs +507 -507
- package/scripts/audit-tool-fallback-policy.mjs +180 -0
- package/scripts/audit-ui-browser-evidence-policy.mjs +191 -0
- 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 -123
- package/scripts/backfill-evidence-levels.mjs +98 -0
- package/scripts/check-encoding.mjs +72 -0
- 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-continue-packet.mjs +1214 -0
- package/scripts/generate-final-acceptance-packet.mjs +188 -173
- package/scripts/generate-final-form-progress-report.mjs +191 -189
- package/scripts/generate-host-runtime-evidence-handoff.mjs +198 -191
- package/scripts/generate-maintenance-snapshot.mjs +216 -0
- package/scripts/generate-owner-external-gate-kit.mjs +295 -295
- package/scripts/generate-public-acceptance-handoff.mjs +168 -156
- 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 -171
- package/scripts/generate-release-status-manifest.mjs +199 -198
- 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 +793 -712
- 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-learning.mjs +37 -8
- 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/record-session-sync.mjs +87 -0
- package/scripts/run-gse-command.mjs +79 -47
- package/scripts/run-validation-profile.mjs +24 -5
- package/scripts/sign-gse-package.mjs +83 -83
- package/scripts/update-project-state.mjs +223 -223
- package/scripts/validate-gse.mjs +216 -0
- package/scripts/verify-gse-package.mjs +85 -85
|
@@ -0,0 +1,1214 @@
|
|
|
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
|
+
import { readProjectGuards } from './audit-project-guards.mjs'
|
|
7
|
+
import { analyzeEvidenceLevels } from './audit-evidence-levels.mjs'
|
|
8
|
+
import { analyzeEvidenceReviewQueue } from './audit-evidence-review-queue.mjs'
|
|
9
|
+
import { readRoleDispatchFallback } from './audit-role-dispatch-fallback.mjs'
|
|
10
|
+
import { auditStateRepair } from './audit-state-repair.mjs'
|
|
11
|
+
import { analyzeLearningPromotions } from './audit-learning-promotion.mjs'
|
|
12
|
+
import { auditLearningDrift } from './audit-learning-drift.mjs'
|
|
13
|
+
import { readHostCapabilities } from './audit-host-capabilities.mjs'
|
|
14
|
+
import { auditToolFallbackPolicy } from './audit-tool-fallback-policy.mjs'
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2)
|
|
17
|
+
|
|
18
|
+
function readArg(name, fallback = null) {
|
|
19
|
+
const index = args.indexOf(name)
|
|
20
|
+
if (index === -1) return fallback
|
|
21
|
+
return args[index + 1] ?? fallback
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const root = path.resolve(readArg('--root', path.join(import.meta.dirname, '..')))
|
|
25
|
+
const targetArg = readArg('--target')
|
|
26
|
+
const jsonOnly = args.includes('--json')
|
|
27
|
+
const selfTest = args.includes('--self-test') || !targetArg
|
|
28
|
+
|
|
29
|
+
function readText(filePath) {
|
|
30
|
+
return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '') : ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readJson(filePath) {
|
|
34
|
+
if (!fs.existsSync(filePath)) return { exists: false, ok: false, data: null, error: 'missing' }
|
|
35
|
+
try {
|
|
36
|
+
return { exists: true, ok: true, data: JSON.parse(readText(filePath)), error: '' }
|
|
37
|
+
} catch (error) {
|
|
38
|
+
return { exists: true, ok: false, data: null, error: error.message }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readJsonl(filePath) {
|
|
43
|
+
if (!fs.existsSync(filePath)) return { exists: false, ok: false, records: [], error: 'missing' }
|
|
44
|
+
const lines = readText(filePath)
|
|
45
|
+
.split(/\r?\n/)
|
|
46
|
+
.map((line) => line.trim())
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
const records = []
|
|
49
|
+
for (const [index, line] of lines.entries()) {
|
|
50
|
+
try {
|
|
51
|
+
records.push(JSON.parse(line))
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return { exists: true, ok: false, records, error: `line ${index + 1}: ${error.message}` }
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { exists: true, ok: true, records, error: '' }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function readMaintenanceSnapshot(target) {
|
|
60
|
+
const snapshotPath = path.join(target, '.gse', 'maintenance', 'latest-maintenance-snapshot.json')
|
|
61
|
+
const snapshot = readJson(snapshotPath)
|
|
62
|
+
if (!snapshot.ok) {
|
|
63
|
+
return {
|
|
64
|
+
exists: snapshot.exists,
|
|
65
|
+
status: snapshot.exists ? 'invalid' : 'missing',
|
|
66
|
+
generatedAt: null,
|
|
67
|
+
summary: null,
|
|
68
|
+
installedSync: null,
|
|
69
|
+
releaseBundleFreshness: null,
|
|
70
|
+
installedSyncMode: 'unknown',
|
|
71
|
+
path: snapshotPath,
|
|
72
|
+
error: snapshot.error,
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const data = snapshot.data
|
|
76
|
+
return {
|
|
77
|
+
exists: true,
|
|
78
|
+
status: data?.summary?.status ?? 'unknown',
|
|
79
|
+
generatedAt: data?.generatedAt ?? null,
|
|
80
|
+
summary: data?.summary ?? null,
|
|
81
|
+
installedSync: data?.workflows?.installedSync ?? 'unknown',
|
|
82
|
+
releaseBundleFreshness: data?.workflows?.releaseBundleFreshness ?? 'unknown',
|
|
83
|
+
installedSyncMode: data?.summary?.installedSyncMode ?? 'unknown',
|
|
84
|
+
path: snapshotPath,
|
|
85
|
+
error: '',
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readSessionSyncBoundary(target) {
|
|
90
|
+
const recordPath = path.join(target, '.gse', 'session-sync.jsonl')
|
|
91
|
+
const parsed = readJsonl(recordPath)
|
|
92
|
+
if (!parsed.exists) {
|
|
93
|
+
return {
|
|
94
|
+
exists: false,
|
|
95
|
+
valid: true,
|
|
96
|
+
totalRecords: 0,
|
|
97
|
+
statusCounts: {},
|
|
98
|
+
installedSyncRecorded: false,
|
|
99
|
+
latestInstalledSync: null,
|
|
100
|
+
latestThreadOutcomes: [],
|
|
101
|
+
adoptionProven: false,
|
|
102
|
+
boundary: 'no-session-sync-records',
|
|
103
|
+
latestRecords: [],
|
|
104
|
+
error: '',
|
|
105
|
+
limits: [
|
|
106
|
+
'Missing session sync records mean no active-session notification evidence is being claimed.',
|
|
107
|
+
'Installed-copy sync and cross-session adoption are separate claims.',
|
|
108
|
+
],
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!parsed.ok) {
|
|
112
|
+
return {
|
|
113
|
+
exists: true,
|
|
114
|
+
valid: false,
|
|
115
|
+
totalRecords: parsed.records.length,
|
|
116
|
+
statusCounts: {},
|
|
117
|
+
installedSyncRecorded: false,
|
|
118
|
+
latestInstalledSync: null,
|
|
119
|
+
latestThreadOutcomes: [],
|
|
120
|
+
adoptionProven: false,
|
|
121
|
+
boundary: 'invalid-session-sync-records',
|
|
122
|
+
latestRecords: [],
|
|
123
|
+
error: parsed.error,
|
|
124
|
+
limits: [
|
|
125
|
+
'Invalid session sync JSONL cannot support installed-copy or active-session sync claims.',
|
|
126
|
+
'Repair .gse/session-sync.jsonl before relying on sync evidence.',
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const statusCounts = {}
|
|
132
|
+
for (const record of parsed.records) {
|
|
133
|
+
const status = record.status || 'unknown'
|
|
134
|
+
statusCounts[status] = (statusCounts[status] || 0) + 1
|
|
135
|
+
}
|
|
136
|
+
const installedRecords = parsed.records.filter((record) => record.status === 'installed-sync')
|
|
137
|
+
const threadMap = new Map()
|
|
138
|
+
for (const record of parsed.records) {
|
|
139
|
+
if (record.threadId) threadMap.set(record.threadId, record)
|
|
140
|
+
}
|
|
141
|
+
const latestThreadOutcomes = [...threadMap.entries()].map(([threadId, record]) => ({
|
|
142
|
+
threadId,
|
|
143
|
+
status: record.status,
|
|
144
|
+
method: record.method,
|
|
145
|
+
project: record.project ?? null,
|
|
146
|
+
recordedAt: record.recordedAt ?? null,
|
|
147
|
+
}))
|
|
148
|
+
const adoptionProven = parsed.records.some((record) => record.adoptionProven === true || record.adoptionStatus === 'verified')
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
exists: true,
|
|
152
|
+
valid: true,
|
|
153
|
+
totalRecords: parsed.records.length,
|
|
154
|
+
statusCounts,
|
|
155
|
+
installedSyncRecorded: installedRecords.length > 0,
|
|
156
|
+
latestInstalledSync: installedRecords.at(-1) ?? null,
|
|
157
|
+
latestThreadOutcomes,
|
|
158
|
+
adoptionProven,
|
|
159
|
+
boundary: adoptionProven ? 'adoption-recorded' : 'sync-records-do-not-prove-adoption',
|
|
160
|
+
latestRecords: parsed.records.slice(-5),
|
|
161
|
+
error: '',
|
|
162
|
+
limits: [
|
|
163
|
+
'Session sync records prove notification attempts or installed-copy parity, not target-session adoption by default.',
|
|
164
|
+
'A target session is adopted only when a future record explicitly carries adoptionProven=true or adoptionStatus=verified with matching evidence.',
|
|
165
|
+
'Unavailable, skipped, failed, archived, and sent outcomes must stay honest transport outcomes rather than adoption claims.',
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function exists(target, relativePath) {
|
|
171
|
+
return fs.existsSync(path.join(target, relativePath))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function firstMatch(text, regex) {
|
|
175
|
+
const match = text.match(regex)
|
|
176
|
+
return match ? match[1].trim() : ''
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function cleanInlineValue(value) {
|
|
180
|
+
return String(value || '').trim().replace(/[.銆?锛歖]+$/, '').trim()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function findCanonicalPlan(target, state) {
|
|
184
|
+
if (state?.canonicalPlan) return state.canonicalPlan
|
|
185
|
+
const texts = [
|
|
186
|
+
readText(path.join(target, '.gse', 'README.md')),
|
|
187
|
+
readText(path.join(target, '.gse', 'project-profile.md')),
|
|
188
|
+
readText(path.join(target, '.gse', 'goal-map.md')),
|
|
189
|
+
].join('\n')
|
|
190
|
+
const match = texts.match(/docs\/[A-Za-z0-9._/-]*?(?:architecture|productization)[A-Za-z0-9._/-]*?\.md/)
|
|
191
|
+
if (match) return match[0]
|
|
192
|
+
for (const fallback of [
|
|
193
|
+
'docs/aion-productization-architecture.md',
|
|
194
|
+
'docs/museflow-ai-film-productization-architecture.md',
|
|
195
|
+
]) {
|
|
196
|
+
if (exists(target, fallback)) return fallback
|
|
197
|
+
}
|
|
198
|
+
return ''
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function runNode(script, commandArgs, cwd = root) {
|
|
202
|
+
const result = spawnSync(process.execPath, [path.join(root, 'scripts', script), ...commandArgs], {
|
|
203
|
+
cwd,
|
|
204
|
+
encoding: 'utf8',
|
|
205
|
+
windowsHide: true,
|
|
206
|
+
})
|
|
207
|
+
const stdout = (result.stdout ?? '').trim()
|
|
208
|
+
let data = null
|
|
209
|
+
try {
|
|
210
|
+
data = JSON.parse(stdout)
|
|
211
|
+
} catch {
|
|
212
|
+
data = null
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
command: [process.execPath, path.join(root, 'scripts', script), ...commandArgs].join(' '),
|
|
216
|
+
status: result.status ?? 1,
|
|
217
|
+
ok: (result.status ?? 1) === 0,
|
|
218
|
+
data,
|
|
219
|
+
stdout,
|
|
220
|
+
stderr: (result.stderr ?? '').trim(),
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function listGitChanges(target) {
|
|
225
|
+
const result = spawnSync('git', ['-C', target, 'status', '--porcelain=v1'], {
|
|
226
|
+
cwd: target,
|
|
227
|
+
encoding: 'utf8',
|
|
228
|
+
windowsHide: true,
|
|
229
|
+
})
|
|
230
|
+
if ((result.status ?? 1) !== 0) {
|
|
231
|
+
return {
|
|
232
|
+
status: 'unavailable',
|
|
233
|
+
paths: [],
|
|
234
|
+
error: (result.stderr ?? '').trim() || 'git status failed',
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const entries = (result.stdout ?? '')
|
|
238
|
+
.split(/\r?\n/)
|
|
239
|
+
.filter((line) => line.trim())
|
|
240
|
+
.map((line) => {
|
|
241
|
+
const x = line[0] ?? ' '
|
|
242
|
+
const y = line[1] ?? ' '
|
|
243
|
+
const rawPath = line.slice(3).replace(/\\/g, '/')
|
|
244
|
+
const normalizedPath = rawPath.includes(' -> ') ? rawPath.split(' -> ').pop() : rawPath
|
|
245
|
+
return {
|
|
246
|
+
code: x + y,
|
|
247
|
+
path: normalizedPath,
|
|
248
|
+
staged: x !== ' ' && x !== '?',
|
|
249
|
+
unstaged: y !== ' ' && y !== '?',
|
|
250
|
+
untracked: x === '?' && y === '?',
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
return { status: 'available', entries, paths: entries.map((entry) => entry.path), error: '' }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function hasPackageScript(target, scriptName) {
|
|
257
|
+
const packageJson = readJson(path.join(target, 'package.json'))
|
|
258
|
+
return Boolean(packageJson.ok && packageJson.data?.scripts?.[scriptName])
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const generatedArtifactPatterns = [
|
|
262
|
+
/^node_modules\//,
|
|
263
|
+
/^output\//,
|
|
264
|
+
/^playwright-report\//,
|
|
265
|
+
/^test-results\//,
|
|
266
|
+
/^coverage\//,
|
|
267
|
+
/^\.nyc_output\//,
|
|
268
|
+
/^\.turbo\//,
|
|
269
|
+
/^\.next\//,
|
|
270
|
+
/^dist\//,
|
|
271
|
+
/^build\//,
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
const transientLockfiles = new Set([
|
|
275
|
+
'pnpm-lock.yaml',
|
|
276
|
+
'package-lock.json',
|
|
277
|
+
'yarn.lock',
|
|
278
|
+
'bun.lockb',
|
|
279
|
+
])
|
|
280
|
+
|
|
281
|
+
function isGeneratedOrTransientChange(entry) {
|
|
282
|
+
const normalized = entry.path.replace(/\\/g, '/')
|
|
283
|
+
if (generatedArtifactPatterns.some((pattern) => pattern.test(normalized))) return true
|
|
284
|
+
return entry.untracked && transientLockfiles.has(normalized)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function buildCompletionPlan(target, state, maintenanceSnapshot) {
|
|
288
|
+
const gitChanges = listGitChanges(target)
|
|
289
|
+
const changedEntries = gitChanges.entries ?? gitChanges.paths.map((item) => ({ path: item, untracked: false, staged: false, unstaged: true }))
|
|
290
|
+
const ignoredGeneratedPaths = changedEntries.filter(isGeneratedOrTransientChange).map((entry) => entry.path)
|
|
291
|
+
const actionableEntries = changedEntries.filter((entry) => !isGeneratedOrTransientChange(entry))
|
|
292
|
+
const changedPaths = actionableEntries.map((entry) => entry.path)
|
|
293
|
+
const hasGseScripts = fs.existsSync(path.join(target, 'scripts', 'run-gse-command.mjs'))
|
|
294
|
+
const hasEncodingCheck = hasPackageScript(target, 'check:encoding')
|
|
295
|
+
const validationProfile = state?.mode === 'enterprise' ? 'lite' : 'lite'
|
|
296
|
+
const docsOrLearningChanged = changedPaths.some((item) =>
|
|
297
|
+
item === 'SKILL.md' ||
|
|
298
|
+
item === 'README.md' ||
|
|
299
|
+
item === 'README.zh-CN.md' ||
|
|
300
|
+
item === 'CHANGELOG.md' ||
|
|
301
|
+
item.startsWith('docs/') ||
|
|
302
|
+
item.startsWith('references/') ||
|
|
303
|
+
item.startsWith('.gse/') ||
|
|
304
|
+
/\.(md|mdx|json|jsonl|ya?ml)$/i.test(item),
|
|
305
|
+
)
|
|
306
|
+
const capabilityChanged = changedPaths.some((item) =>
|
|
307
|
+
item === 'SKILL.md' ||
|
|
308
|
+
item === 'package.json' ||
|
|
309
|
+
item.startsWith('scripts/') ||
|
|
310
|
+
item.startsWith('references/') ||
|
|
311
|
+
item.startsWith('assets/') ||
|
|
312
|
+
item.startsWith('agents/') ||
|
|
313
|
+
item.startsWith('.github/') ||
|
|
314
|
+
item === 'README.md' ||
|
|
315
|
+
item === 'README.zh-CN.md',
|
|
316
|
+
)
|
|
317
|
+
const releaseSensitiveChanged = changedPaths.some((item) =>
|
|
318
|
+
item.startsWith('.gse/acceptance/') ||
|
|
319
|
+
item.startsWith('.gse/release-bundles/') ||
|
|
320
|
+
item === 'references/' ||
|
|
321
|
+
item.startsWith('references/') ||
|
|
322
|
+
item.startsWith('assets/marketplace/') ||
|
|
323
|
+
item.startsWith('scripts/generate-release') ||
|
|
324
|
+
item.startsWith('scripts/audit-release') ||
|
|
325
|
+
item.startsWith('scripts/package-gse') ||
|
|
326
|
+
item.startsWith('scripts/install-gse') ||
|
|
327
|
+
item.includes('public-release') ||
|
|
328
|
+
item.includes('final-readiness') ||
|
|
329
|
+
item.includes('packaging'),
|
|
330
|
+
)
|
|
331
|
+
const installedSyncStale =
|
|
332
|
+
capabilityChanged &&
|
|
333
|
+
hasGseScripts &&
|
|
334
|
+
maintenanceSnapshot.installedSyncMode !== 'verified'
|
|
335
|
+
|
|
336
|
+
const requiredCloseCommands = [
|
|
337
|
+
`node scripts/run-validation-profile.mjs --target . --profile ${validationProfile} --json`,
|
|
338
|
+
'node scripts/run-gse-command.mjs --root . --target . --command "/gse close" --json',
|
|
339
|
+
]
|
|
340
|
+
const conditionalCloseCommands = [
|
|
341
|
+
{
|
|
342
|
+
id: 'encoding',
|
|
343
|
+
active: docsOrLearningChanged && hasEncodingCheck,
|
|
344
|
+
when: 'docs, markdown, JSON/JSONL, YAML, evidence, or learning files changed and package.json exposes check:encoding',
|
|
345
|
+
command: 'cmd /c pnpm check:encoding',
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: 'installed-sync',
|
|
349
|
+
active: capabilityChanged && hasGseScripts,
|
|
350
|
+
when: 'GSE capability files changed and an installed skill copy must stay fresh',
|
|
351
|
+
command: 'node scripts/audit-installed-sync.mjs --root . --installed-root C:\\Users\\Admin\\.codex\\skills\\gse --json',
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
id: 'maintenance-snapshot',
|
|
355
|
+
active: (capabilityChanged || releaseSensitiveChanged) && hasGseScripts,
|
|
356
|
+
when: 'capability, release, package, or maintenance-sensitive files changed',
|
|
357
|
+
command: releaseSensitiveChanged
|
|
358
|
+
? 'node scripts/generate-maintenance-snapshot.mjs --root . --target . --installed-root C:\\Users\\Admin\\.codex\\skills\\gse --execute --json'
|
|
359
|
+
: 'node scripts/generate-maintenance-snapshot.mjs --root . --target . --installed-root C:\\Users\\Admin\\.codex\\skills\\gse --skip-release-bundle --execute --json',
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: 'release-bundle',
|
|
363
|
+
active: releaseSensitiveChanged && hasGseScripts,
|
|
364
|
+
when: 'release, packaging, public acceptance, marketplace, or final-readiness files changed',
|
|
365
|
+
command: 'node scripts/generate-release-bundle.mjs --root . --label gse-release-bundle-audit --out .gse\\release-bundles\\gse-release-bundle-audit --force --json',
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: 'session-sync',
|
|
369
|
+
active: capabilityChanged && hasGseScripts && fs.existsSync(path.join(target, '.gse', 'session-sync.jsonl')),
|
|
370
|
+
when: 'installed GSE capability changed and active GSE-using session sync records are maintained',
|
|
371
|
+
command: 'node scripts/audit-session-sync.mjs --root . --json',
|
|
372
|
+
},
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
status: gitChanges.status,
|
|
377
|
+
profile: validationProfile,
|
|
378
|
+
changedPathCount: changedPaths.length,
|
|
379
|
+
changedPaths: changedPaths.slice(0, 20),
|
|
380
|
+
ignoredGeneratedPathCount: ignoredGeneratedPaths.length,
|
|
381
|
+
ignoredGeneratedPaths: ignoredGeneratedPaths.slice(0, 20),
|
|
382
|
+
requiredSteps: [
|
|
383
|
+
'Record outcome, scope, acceptance, evidence, risk, and next action for the current slice.',
|
|
384
|
+
'Append evidence to .gse/evidence/YYYY-MM-DD.md and .gse/evidence/index.jsonl.',
|
|
385
|
+
'Update .gse/state.json, .gse/current-slice.md, and .gse/goal-map.md before close.',
|
|
386
|
+
'Commit only the current slice files after focused checks pass.',
|
|
387
|
+
],
|
|
388
|
+
requiredCloseCommands,
|
|
389
|
+
conditionalCloseCommands,
|
|
390
|
+
activeCloseCommands: conditionalCloseCommands.filter((item) => item.active).map((item) => item.command),
|
|
391
|
+
staleSignals: {
|
|
392
|
+
installedSyncStale,
|
|
393
|
+
maintenanceSnapshotStatus: maintenanceSnapshot.status,
|
|
394
|
+
maintenanceSnapshotMode: maintenanceSnapshot.installedSyncMode,
|
|
395
|
+
},
|
|
396
|
+
limits: [
|
|
397
|
+
'These commands are a close checklist, not proof that the slice is complete until their outputs are recorded as evidence.',
|
|
398
|
+
'Host-native slash-command evidence stays optional per host adapter and is not part of the default close checklist.',
|
|
399
|
+
],
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function buildGateTaxonomy(blockedGates) {
|
|
404
|
+
return {
|
|
405
|
+
core: {
|
|
406
|
+
scope: 'default project continuation and slice close',
|
|
407
|
+
blocking: true,
|
|
408
|
+
examples: [
|
|
409
|
+
'invalid .gse/state.json',
|
|
410
|
+
'invalid .gse/evidence/index.jsonl',
|
|
411
|
+
'missing project profile, goal map, quality gates, or canonical plan',
|
|
412
|
+
'missing current-slice evidence for the claim being closed',
|
|
413
|
+
],
|
|
414
|
+
source: 'continue preflight, quality gates, close gate, project guards',
|
|
415
|
+
},
|
|
416
|
+
release: {
|
|
417
|
+
scope: 'public release, package, registry, CI, repository, security, or marketplace claims',
|
|
418
|
+
blocking: blockedGates.some((gate) => gate.gateType === 'owner-external'),
|
|
419
|
+
pendingCount: blockedGates.filter((gate) => gate.gateType === 'owner-external').length,
|
|
420
|
+
examples: [
|
|
421
|
+
'public CI run',
|
|
422
|
+
'public repository settings',
|
|
423
|
+
'public security contact',
|
|
424
|
+
'registry or package publication',
|
|
425
|
+
'marketplace or catalog publication',
|
|
426
|
+
],
|
|
427
|
+
source: 'final readiness matrix and accepted owner/external records',
|
|
428
|
+
},
|
|
429
|
+
hostAdapter: {
|
|
430
|
+
scope: 'native slash-command, host UI, MCP, LSP, browser, subagent, or other runtime adapter claims',
|
|
431
|
+
blocking: blockedGates.some((gate) => gate.blockingScope === 'public-or-host-claim'),
|
|
432
|
+
examples: [
|
|
433
|
+
'native /gse command in a specific host',
|
|
434
|
+
'host UI command-palette invocation',
|
|
435
|
+
'verified browser or Playwright runtime',
|
|
436
|
+
'verified subagent dispatch',
|
|
437
|
+
],
|
|
438
|
+
source: 'host capability records and host runtime invocation records',
|
|
439
|
+
},
|
|
440
|
+
rule: 'Core gates block default continuation; release and host-adapter gates block only the specific public, release, or host claim unless the project owner explicitly promotes them to project policy.',
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function buildShortPrompt({ projectName, resolvedTarget, preflightStatus, compactState, blockedGates, failedHardChecks }) {
|
|
445
|
+
const nextMode = compactState.nextSliceMode?.action === 'open-next-slice'
|
|
446
|
+
? 'open-next-slice'
|
|
447
|
+
: 'continue-current-slice'
|
|
448
|
+
const lines = [
|
|
449
|
+
`GSE continue: ${projectName} | ${compactState.phase} | preflight=${preflightStatus}`,
|
|
450
|
+
`Root: ${resolvedTarget}`,
|
|
451
|
+
`Slice: ${compactState.currentSlice.outcome} (${compactState.currentSlice.status})`,
|
|
452
|
+
`Next: ${compactState.currentSlice.nextAction}`,
|
|
453
|
+
`Mode: ${nextMode} | close=${compactState.completionPlan.requiredCloseCommands.length} required/${compactState.completionPlan.activeCloseCommands.length} active | risks=${compactState.activeRiskCount} active/${compactState.archivedRiskCount} archived | gates core=${compactState.gateTaxonomy.core.blocking ? 'blocking' : 'open'}, release=${compactState.gateTaxonomy.release.pendingCount}, host=${compactState.gateTaxonomy.hostAdapter.blocking ? 'claim-gated' : 'optional'}`,
|
|
454
|
+
]
|
|
455
|
+
if (blockedGates.length) lines.push(`Claim evidence: ${blockedGates.map((gate) => gate.area).join(', ')}`)
|
|
456
|
+
if (failedHardChecks.length) lines.push(`Fix first: ${failedHardChecks.map((item) => item.label).join('; ')}`)
|
|
457
|
+
lines.push('Do: one verifiable slice -> evidence -> state/goal-map/current-slice -> completionPlan checks -> commit.')
|
|
458
|
+
return lines.join('\n')
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function buildNextSliceMode(state) {
|
|
462
|
+
const status = String(state?.currentSlice?.status ?? '').trim().toLowerCase()
|
|
463
|
+
const verifiedStatuses = new Set(['verified', 'accepted', 'archived', 'closed', 'complete', 'completed'])
|
|
464
|
+
const currentSliceVerified = verifiedStatuses.has(status)
|
|
465
|
+
return {
|
|
466
|
+
action: currentSliceVerified ? 'open-next-slice' : 'continue-current-slice',
|
|
467
|
+
currentSliceVerified,
|
|
468
|
+
reason: currentSliceVerified
|
|
469
|
+
? 'Current slice is already verified; use nextAction and roadmap priority to open a new verifiable slice before implementation.'
|
|
470
|
+
: 'Current slice is not verified; continue or close the active slice before opening another one.',
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function shortText(value, maxLength = 180) {
|
|
475
|
+
const normalized = String(value || '').replace(/`/g, '').replace(/\s+/g, ' ').trim()
|
|
476
|
+
if (normalized.length <= maxLength) return normalized
|
|
477
|
+
return normalized.slice(0, maxLength - 1).trimEnd() + '…'
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function fullCandidateReason(value) {
|
|
481
|
+
return String(value || '').replace(/`/g, '').replace(/\s+/g, ' ').trim()
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function extractBulletsFromSection(text, heading) {
|
|
485
|
+
const lines = String(text || '').split(/\r?\n/)
|
|
486
|
+
const start = lines.findIndex((line) => line.trim().toLowerCase() === heading.toLowerCase())
|
|
487
|
+
if (start === -1) return []
|
|
488
|
+
const bullets = []
|
|
489
|
+
for (const line of lines.slice(start + 1)) {
|
|
490
|
+
if (/^#{1,6}\s+/.test(line)) break
|
|
491
|
+
const match = line.match(/^\s*-\s+(.+)/)
|
|
492
|
+
if (match) bullets.push(match[1].trim())
|
|
493
|
+
}
|
|
494
|
+
return bullets
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function isHostNativeOnlyCandidate(text) {
|
|
498
|
+
const value = String(text || '').toLowerCase()
|
|
499
|
+
return (
|
|
500
|
+
value.includes('native slash-command') ||
|
|
501
|
+
value.includes('host-native') ||
|
|
502
|
+
value.includes('host native') ||
|
|
503
|
+
value.includes('host ui') ||
|
|
504
|
+
value.includes('browser') ||
|
|
505
|
+
value.includes('mcp') ||
|
|
506
|
+
value.includes('lsp') ||
|
|
507
|
+
value.includes('subagent') ||
|
|
508
|
+
value.includes('record native slash') ||
|
|
509
|
+
value.includes('when a host exposes')
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function isMetaNextSliceCandidate(text) {
|
|
514
|
+
const value = String(text || '').toLowerCase()
|
|
515
|
+
return (
|
|
516
|
+
value.includes('open the next') ||
|
|
517
|
+
value.includes('next verifiable slice') ||
|
|
518
|
+
value.includes('next-slice selector') ||
|
|
519
|
+
value.includes('next slice selector') ||
|
|
520
|
+
value.includes('use /gse continue candidates') ||
|
|
521
|
+
value.includes('roadmap priority') ||
|
|
522
|
+
value.includes('harden candidate ranking')
|
|
523
|
+
)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function uniqueCandidateReasons(items) {
|
|
527
|
+
const seen = new Set()
|
|
528
|
+
const output = []
|
|
529
|
+
for (const item of items) {
|
|
530
|
+
const fullReason = fullCandidateReason(item.fullReason || item.reason)
|
|
531
|
+
if (!fullReason) continue
|
|
532
|
+
const key = fullReason.toLowerCase()
|
|
533
|
+
if (seen.has(key)) continue
|
|
534
|
+
seen.add(key)
|
|
535
|
+
output.push({ ...item, fullReason })
|
|
536
|
+
}
|
|
537
|
+
return output
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function buildCandidateActionPacket(candidate) {
|
|
541
|
+
const kind = candidate.kind || 'roadmap-gap'
|
|
542
|
+
const base = {
|
|
543
|
+
candidateType: kind,
|
|
544
|
+
outcomeHint: candidate.outcomeHint || 'Open a focused GSE slice that makes the selected gap measurably more true.',
|
|
545
|
+
scopeHint: candidate.scopeHint || 'Touch only the scripts, references, templates, and state files needed for this slice.',
|
|
546
|
+
acceptanceHint: candidate.acceptanceHint,
|
|
547
|
+
evidenceHint: candidate.evidenceHint || 'Add or update a focused audit, run /gse continue, record evidence, and keep the claim boundary explicit.',
|
|
548
|
+
riskHint: candidate.riskHint || 'Do not turn optional host, release, or project-specific evidence into a default core blocker.',
|
|
549
|
+
nextActionHint: candidate.nextActionHint || 'Update current-slice, state, goal-map, evidence, installed copy, and session sync records when the capability changes.',
|
|
550
|
+
focusedChecks: candidate.focusedChecks || [
|
|
551
|
+
'node scripts\\audit-continue-preflight.mjs --root . --json',
|
|
552
|
+
'node scripts\\run-validation-profile.mjs --target . --profile lite --json',
|
|
553
|
+
],
|
|
554
|
+
}
|
|
555
|
+
return base
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function buildNextSliceCandidates(target, state, nextSliceMode) {
|
|
559
|
+
if (nextSliceMode.action !== 'open-next-slice') return []
|
|
560
|
+
|
|
561
|
+
const roadmapText = readText(path.join(target, 'references', 'final-form-roadmap.md'))
|
|
562
|
+
const goalMapText = readText(path.join(target, '.gse', 'goal-map.md'))
|
|
563
|
+
const currentNextAction = fullCandidateReason(state?.currentSlice?.nextAction || 'Open the next verifiable slice from the current roadmap priority.')
|
|
564
|
+
const activeRisks = Array.isArray(state?.residualRisks) ? state.residualRisks.map((risk) => fullCandidateReason(risk)) : []
|
|
565
|
+
const roadmapBullets = [
|
|
566
|
+
...extractBulletsFromSection(roadmapText, '## Current Final-Form Gap List'),
|
|
567
|
+
...extractBulletsFromSection(roadmapText, '## Final Form Priorities'),
|
|
568
|
+
].filter((item) => item && !/^Done:/i.test(item) && !isHostNativeOnlyCandidate(item))
|
|
569
|
+
const goalMapNext = firstMatch(goalMapText, /Next action:\s*([^\n]+)/i)
|
|
570
|
+
|
|
571
|
+
const candidates = []
|
|
572
|
+
const firstReason = goalMapNext ? fullCandidateReason(goalMapNext) : currentNextAction
|
|
573
|
+
|
|
574
|
+
const roadmapCandidate = roadmapBullets.find((item) =>
|
|
575
|
+
item.toLowerCase().includes('short entry') ||
|
|
576
|
+
item.toLowerCase().includes('continue') ||
|
|
577
|
+
item.toLowerCase().includes('state') ||
|
|
578
|
+
item.toLowerCase().includes('evidence') ||
|
|
579
|
+
item.toLowerCase().includes('learning') ||
|
|
580
|
+
item.toLowerCase().includes('maintenance')
|
|
581
|
+
) || roadmapBullets[0]
|
|
582
|
+
|
|
583
|
+
if (roadmapCandidate) {
|
|
584
|
+
candidates.push({
|
|
585
|
+
kind: 'roadmap-gap',
|
|
586
|
+
title: 'Reduce the next short-entry handoff gap',
|
|
587
|
+
source: 'references/final-form-roadmap.md',
|
|
588
|
+
fullReason: fullCandidateReason(roadmapCandidate),
|
|
589
|
+
outcomeHint: 'Make the next /gse continue candidate directly openable as a slice without a long prompt rewrite.',
|
|
590
|
+
scopeHint: 'Add machine-readable action fields to next-slice candidates and audit their presence.',
|
|
591
|
+
acceptanceHint: 'Add executable support or a focused audit for the selected roadmap gap, then surface it through /gse continue or /gse close when relevant.',
|
|
592
|
+
evidenceHint: 'Focused continue-preflight audit proves candidates include action fields and real /gse continue output exposes them.',
|
|
593
|
+
riskHint: 'Candidate packets can still be hints, not autonomous roadmap decisions.',
|
|
594
|
+
nextActionHint: 'Use the structured candidate packet to create the next current-slice entry.',
|
|
595
|
+
suggestedProfile: 'lite',
|
|
596
|
+
})
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const riskCandidate = activeRisks.find((risk) => !isHostNativeOnlyCandidate(risk))
|
|
600
|
+
if (riskCandidate) {
|
|
601
|
+
candidates.push({
|
|
602
|
+
kind: 'active-risk',
|
|
603
|
+
title: 'Turn an active risk into an executable guard',
|
|
604
|
+
source: '.gse/state.json residualRisks',
|
|
605
|
+
fullReason: riskCandidate,
|
|
606
|
+
outcomeHint: 'Reduce one active recurring risk by converting it into an auditable guard, repair action, or claim boundary.',
|
|
607
|
+
scopeHint: 'Change one guard/audit/continue-close surface and compact the active risk only after evidence passes.',
|
|
608
|
+
acceptanceHint: 'Move one recurring active risk into a guard, audit, repair action, or explicit claim boundary, then reduce the active-risk summary.',
|
|
609
|
+
evidenceHint: 'Focused audit shows the risk is enforced or surfaced before close; state shows the active risk no longer needs top-risk space.',
|
|
610
|
+
riskHint: 'Avoid deleting risk text unless the new guard actually prevents or surfaces the failure.',
|
|
611
|
+
nextActionHint: 'Pick the highest-value active risk and open a guard-hardening slice.',
|
|
612
|
+
suggestedProfile: 'lite',
|
|
613
|
+
})
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (firstReason && (!isMetaNextSliceCandidate(firstReason) || candidates.length === 0)) {
|
|
617
|
+
candidates.push({
|
|
618
|
+
kind: 'goal-map-next',
|
|
619
|
+
title: 'Open the next roadmap-backed slice',
|
|
620
|
+
source: goalMapNext ? '.gse/goal-map.md Current Focus' : '.gse/state.json currentSlice.nextAction',
|
|
621
|
+
fullReason: firstReason,
|
|
622
|
+
outcomeHint: 'Turn the recorded next action into a concrete current slice before implementation.',
|
|
623
|
+
scopeHint: 'Create or update only the slice planning fields needed to start work.',
|
|
624
|
+
acceptanceHint: 'Create a new current-slice entry with outcome, scope, acceptance, evidence, risk, and next action before implementation.',
|
|
625
|
+
evidenceHint: 'Continue packet shows the new slice as active and close checks know which evidence is required.',
|
|
626
|
+
riskHint: 'Do not start coding from a vague next action without a verifiable slice contract.',
|
|
627
|
+
nextActionHint: 'Write the current-slice contract, then implement the smallest evidence-backed change.',
|
|
628
|
+
suggestedProfile: 'lite',
|
|
629
|
+
})
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
return uniqueCandidateReasons(candidates)
|
|
633
|
+
.slice(0, 3)
|
|
634
|
+
.map((candidate, index) => ({
|
|
635
|
+
id: `NEXT-${String(index + 1).padStart(3, '0')}`,
|
|
636
|
+
title: candidate.title,
|
|
637
|
+
source: candidate.source,
|
|
638
|
+
reason: shortText(candidate.fullReason),
|
|
639
|
+
fullReason: candidate.fullReason,
|
|
640
|
+
acceptanceHint: candidate.acceptanceHint,
|
|
641
|
+
actionPacket: buildCandidateActionPacket(candidate),
|
|
642
|
+
suggestedProfile: candidate.suggestedProfile,
|
|
643
|
+
}))
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function makeCheck(id, label, ok, evidence, severity = 'hard', recommendation = '') {
|
|
647
|
+
return {
|
|
648
|
+
id,
|
|
649
|
+
label,
|
|
650
|
+
status: ok ? 'passed' : severity === 'soft' ? 'warning' : 'failed',
|
|
651
|
+
severity,
|
|
652
|
+
evidence,
|
|
653
|
+
recommendation,
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function createFixture(options = {}) {
|
|
658
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gse-continue-packet-'))
|
|
659
|
+
fs.mkdirSync(path.join(dir, '.gse', 'evidence'), { recursive: true })
|
|
660
|
+
fs.mkdirSync(path.join(dir, 'docs'), { recursive: true })
|
|
661
|
+
fs.writeFileSync(path.join(dir, 'AGENTS.md'), '# Rules\n', 'utf8')
|
|
662
|
+
fs.writeFileSync(path.join(dir, '.gse', 'README.md'), '# GSE\n\nCanonical plan: `docs/productization-architecture.md`.\n', 'utf8')
|
|
663
|
+
fs.writeFileSync(path.join(dir, '.gse', 'project-profile.md'), '# Project Profile\n\n- Product/system name: Fixture Product\n', 'utf8')
|
|
664
|
+
fs.writeFileSync(path.join(dir, '.gse', 'goal-map.md'), '# Goal Map\n\n## Current Focus\n\n- Active slice: Fixture continue.\n- Next action: Run fixture smoke.\n', 'utf8')
|
|
665
|
+
fs.writeFileSync(path.join(dir, '.gse', 'quality-gates.md'), '# Quality Gates\n\n- Evidence required.\n', 'utf8')
|
|
666
|
+
fs.writeFileSync(path.join(dir, '.gse', 'host-capabilities.md'), [
|
|
667
|
+
'# Host Capabilities',
|
|
668
|
+
'',
|
|
669
|
+
'| Capability | Host/Tool | Status | Evidence | Claim Boundary | Last Checked |',
|
|
670
|
+
'|---|---|---|---|---|---|',
|
|
671
|
+
'| native-slash-command | current host | external-required | - | Native slash-command support requires real host runtime invocation evidence, not portable `/gse` runner output. | - |',
|
|
672
|
+
'| browser | browser or Playwright | unknown | - | Browser proof requires a real browser/component/screenshot command for this project. | - |',
|
|
673
|
+
'| mcp | MCP servers | unknown | - | MCP status is host and project specific. | - |',
|
|
674
|
+
'| lsp | LSP or code index | unknown | - | LSP/index status is current-session specific unless project docs prove it. | - |',
|
|
675
|
+
'| subagent | host dispatch | unknown | - | Real subagent dispatch requires verified host/tool evidence; sequential role fallback is not real dispatch. | - |',
|
|
676
|
+
'| ci | project CI | unknown | - | CI is verified only after a workflow/config or run is checked for this project. | - |',
|
|
677
|
+
'',
|
|
678
|
+
].join('\n'), 'utf8')
|
|
679
|
+
fs.writeFileSync(path.join(dir, '.gse', 'project-guards.md'), [
|
|
680
|
+
'# Project Guards',
|
|
681
|
+
'',
|
|
682
|
+
'| ID | Guard | Severity | Trigger | Check | Status |',
|
|
683
|
+
'|---|---|---|---|---|---|',
|
|
684
|
+
'| WIN-SHELL | Use shell syntax that matches the active host. | high | shell command | Confirm shell syntax. | active |',
|
|
685
|
+
'| SPARSE-GIT | Check sparse checkout before staging workflow folders. | high | git staging | Confirm sparse visibility. | active |',
|
|
686
|
+
'| UTF8-DOC | Use UTF-8-safe readers before judging mojibake. | high | docs | Use UTF-8 reader. | active |',
|
|
687
|
+
'| EVIDENCE-STALE | Treat stale evidence as preflight problem. | high | evidence | Validate latest evidence. | active |',
|
|
688
|
+
'| UI-EVIDENCE | Label UI/browser downgrades explicitly. | medium | UI proof | Record evidence level. | active |',
|
|
689
|
+
'| SUBAGENT-HONEST | Do not claim fake subagent dispatch. | high | subagent | Require real host evidence. | active |',
|
|
690
|
+
'| SYNC-NO-INTERRUPT | Do not interrupt running project sessions with cross-thread sync. | high | sync or delegation | Confirm the target session is idle or use owner action record. | active |',
|
|
691
|
+
'',
|
|
692
|
+
].join('\n'), 'utf8')
|
|
693
|
+
fs.writeFileSync(path.join(dir, 'docs', 'productization-architecture.md'), '# Productization Architecture\n', 'utf8')
|
|
694
|
+
fs.writeFileSync(
|
|
695
|
+
path.join(dir, '.gse', 'state.json'),
|
|
696
|
+
JSON.stringify({
|
|
697
|
+
schemaVersion: 1,
|
|
698
|
+
projectName: 'fixture-product',
|
|
699
|
+
mode: 'standard',
|
|
700
|
+
canonicalPlan: 'docs/productization-architecture.md',
|
|
701
|
+
phase: 'execute',
|
|
702
|
+
currentSlice: {
|
|
703
|
+
id: 'fixture-continue',
|
|
704
|
+
outcome: 'Fixture continue packet.',
|
|
705
|
+
status: 'planned',
|
|
706
|
+
nextAction: 'Run fixture smoke.',
|
|
707
|
+
},
|
|
708
|
+
toolStatuses: {
|
|
709
|
+
browser: 'unknown',
|
|
710
|
+
lsp: 'unknown',
|
|
711
|
+
mcp: 'unknown',
|
|
712
|
+
subagents: 'unknown',
|
|
713
|
+
ci: 'unknown',
|
|
714
|
+
},
|
|
715
|
+
lastEvidence: '.gse/evidence/index.jsonl',
|
|
716
|
+
residualRisks: [
|
|
717
|
+
'Fixture top risk 1.',
|
|
718
|
+
'Fixture top risk 2.',
|
|
719
|
+
'Fixture top risk 3.',
|
|
720
|
+
'Fixture archived risk 4.',
|
|
721
|
+
],
|
|
722
|
+
}, null, 2) + '\n',
|
|
723
|
+
'utf8',
|
|
724
|
+
)
|
|
725
|
+
fs.writeFileSync(
|
|
726
|
+
path.join(dir, '.gse', 'evidence', 'index.jsonl'),
|
|
727
|
+
options.invalidEvidence
|
|
728
|
+
? '{"date":"2026-07-08"\n'
|
|
729
|
+
: JSON.stringify({
|
|
730
|
+
date: '2026-07-08',
|
|
731
|
+
recordType: 'slice',
|
|
732
|
+
status: 'verified',
|
|
733
|
+
evidenceLevel: 'verified-unit',
|
|
734
|
+
requiredEvidenceLevel: 'verified-unit',
|
|
735
|
+
summary: 'Fixture evidence.',
|
|
736
|
+
evidenceFile: '.gse/evidence/2026-07-08.md',
|
|
737
|
+
commands: ['fixture'],
|
|
738
|
+
nextAction: 'Run fixture smoke.',
|
|
739
|
+
}) + '\n',
|
|
740
|
+
'utf8',
|
|
741
|
+
)
|
|
742
|
+
return dir
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function buildContinuePacket(target) {
|
|
746
|
+
const resolvedTarget = path.resolve(target)
|
|
747
|
+
const gseDir = path.join(resolvedTarget, '.gse')
|
|
748
|
+
const stateResult = readJson(path.join(gseDir, 'state.json'))
|
|
749
|
+
const state = stateResult.ok ? stateResult.data : null
|
|
750
|
+
const evidenceIndex = readJsonl(path.join(gseDir, 'evidence', 'index.jsonl'))
|
|
751
|
+
const goalMapText = readText(path.join(gseDir, 'goal-map.md'))
|
|
752
|
+
const profileText = readText(path.join(gseDir, 'project-profile.md'))
|
|
753
|
+
const canonicalPlan = findCanonicalPlan(resolvedTarget, state)
|
|
754
|
+
const projectGuards = readProjectGuards(resolvedTarget)
|
|
755
|
+
const roleFallback = readRoleDispatchFallback(resolvedTarget)
|
|
756
|
+
const stateRepair = auditStateRepair(resolvedTarget)
|
|
757
|
+
const learningPromotion = analyzeLearningPromotions(resolvedTarget)
|
|
758
|
+
const learningDrift = auditLearningDrift(resolvedTarget)
|
|
759
|
+
const hostCapabilities = readHostCapabilities(resolvedTarget)
|
|
760
|
+
const toolFallbackPolicy = auditToolFallbackPolicy(resolvedTarget)
|
|
761
|
+
const maintenanceSnapshot = readMaintenanceSnapshot(resolvedTarget)
|
|
762
|
+
const sessionSyncBoundary = readSessionSyncBoundary(resolvedTarget)
|
|
763
|
+
const evidenceLevelAnalysis = analyzeEvidenceLevels(evidenceIndex.records)
|
|
764
|
+
const evidenceReviewQueue = analyzeEvidenceReviewQueue(evidenceIndex.records, resolvedTarget)
|
|
765
|
+
const evidenceLevelIssueCount =
|
|
766
|
+
evidenceLevelAnalysis.invalidLevel.length +
|
|
767
|
+
evidenceLevelAnalysis.downgraded.length +
|
|
768
|
+
evidenceLevelAnalysis.missingLevel.length
|
|
769
|
+
const canonicalPlanExists = canonicalPlan ? exists(resolvedTarget, canonicalPlan) : false
|
|
770
|
+
const evidenceRecordsAreSchemaComplete = evidenceIndex.ok && evidenceIndex.records.length > 0 && evidenceIndex.records.every((record) =>
|
|
771
|
+
typeof record.date === 'string' &&
|
|
772
|
+
typeof record.recordType === 'string' &&
|
|
773
|
+
typeof record.status === 'string' &&
|
|
774
|
+
typeof record.summary === 'string' &&
|
|
775
|
+
typeof record.evidenceFile === 'string' &&
|
|
776
|
+
Array.isArray(record.commands) &&
|
|
777
|
+
typeof record.nextAction === 'string',
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
const checks = [
|
|
781
|
+
makeCheck('CP01', '.gse directory exists', exists(resolvedTarget, '.gse'), exists(resolvedTarget, '.gse') ? '.gse present' : '.gse missing'),
|
|
782
|
+
makeCheck('CP02', 'state.json exists and parses', stateResult.ok, stateResult.exists ? stateResult.ok ? 'valid JSON' : stateResult.error : 'missing'),
|
|
783
|
+
makeCheck('CP03', 'evidence index exists and parses as JSONL', evidenceIndex.ok, evidenceIndex.exists ? evidenceIndex.ok ? `${evidenceIndex.records.length} record(s)` : evidenceIndex.error : 'missing'),
|
|
784
|
+
makeCheck('CP04', 'evidence index records are schema-complete', evidenceRecordsAreSchemaComplete, evidenceRecordsAreSchemaComplete ? 'records include status, evidenceFile, commands, nextAction' : 'missing required fields or no records'),
|
|
785
|
+
makeCheck('CP05', 'project profile exists', exists(resolvedTarget, '.gse/project-profile.md'), '.gse/project-profile.md'),
|
|
786
|
+
makeCheck('CP06', 'goal map exists', exists(resolvedTarget, '.gse/goal-map.md'), '.gse/goal-map.md'),
|
|
787
|
+
makeCheck('CP07', 'quality gates exist', exists(resolvedTarget, '.gse/quality-gates.md'), '.gse/quality-gates.md'),
|
|
788
|
+
makeCheck('CP08', 'canonical plan is declared and present', Boolean(canonicalPlan && canonicalPlanExists), canonicalPlan ? `${canonicalPlan}:${canonicalPlanExists ? 'present' : 'missing'}` : 'not declared'),
|
|
789
|
+
makeCheck('CP09', 'current slice has next action', Boolean(state?.currentSlice?.nextAction || firstMatch(goalMapText, /Next action:\s*([^\n]+)/i)), state?.currentSlice?.nextAction || firstMatch(goalMapText, /Next action:\s*([^\n]+)/i) || 'missing'),
|
|
790
|
+
makeCheck('CP10', 'residual risks are bounded in compact output', Array.isArray(state?.residualRisks), Array.isArray(state?.residualRisks) ? `${state.residualRisks.length} active risk(s); top 3 surfaced` : 'missing residualRisks array', 'soft'),
|
|
791
|
+
makeCheck('CP11', 'project guards are loaded as soft preflight rules', projectGuards.status !== 'failed', projectGuards.exists ? `${projectGuards.active.length} active guard(s)` : 'missing .gse/project-guards.md', 'soft', projectGuards.exists ? '' : 'Add .gse/project-guards.md or run GSE scaffold update when recurring lessons need guard coverage.'),
|
|
792
|
+
makeCheck(
|
|
793
|
+
'CP12',
|
|
794
|
+
'evidence levels are valid and surfaced',
|
|
795
|
+
evidenceLevelIssueCount === 0,
|
|
796
|
+
evidenceLevelAnalysis.invalidLevel.length
|
|
797
|
+
? `invalid evidence level(s): ${evidenceLevelAnalysis.invalidLevel.map((item) => `${item.summary}:${item.evidenceLevel}`).join('; ')}`
|
|
798
|
+
: `${evidenceLevelAnalysis.recordsWithLevel}/${evidenceLevelAnalysis.records} record(s) with evidenceLevel; ${evidenceLevelAnalysis.downgraded.length} downgrade(s); ${evidenceLevelAnalysis.missingLevel.length} historical missing`,
|
|
799
|
+
evidenceLevelAnalysis.invalidLevel.length ? 'hard' : 'soft',
|
|
800
|
+
evidenceLevelAnalysis.invalidLevel.length
|
|
801
|
+
? 'Use one of the evidence levels from references/evidence-taxonomy.md.'
|
|
802
|
+
: 'Add evidenceLevel to new records and label UI/browser/API/CI downgrades explicitly.',
|
|
803
|
+
),
|
|
804
|
+
makeCheck(
|
|
805
|
+
'CP13',
|
|
806
|
+
'historical evidence review queue is visible',
|
|
807
|
+
true,
|
|
808
|
+
`${evidenceReviewQueue.counts.needsReview} needs-review; ${evidenceReviewQueue.counts.eligibleForStrongerReview} eligible-for-stronger-review; ${evidenceReviewQueue.counts.externalRequired} external-required`,
|
|
809
|
+
'soft',
|
|
810
|
+
evidenceReviewQueue.counts.needsReview
|
|
811
|
+
? 'Review conservative historical result records before using them as stronger proof.'
|
|
812
|
+
: 'No conservative historical result records currently need review.',
|
|
813
|
+
),
|
|
814
|
+
makeCheck(
|
|
815
|
+
'CP14',
|
|
816
|
+
'role dispatch fallback packets are auditable',
|
|
817
|
+
roleFallback.status !== 'failed',
|
|
818
|
+
roleFallback.exists ? `${roleFallback.summary.total} role packet(s); ${roleFallback.summary.sequentialFallbackRoles.length} sequential fallback role(s)` : 'missing .gse/agents/role-fallback-packets.md',
|
|
819
|
+
'soft',
|
|
820
|
+
roleFallback.exists
|
|
821
|
+
? ''
|
|
822
|
+
: 'Run a Standard/Enterprise GSE scaffold update or add role fallback packets before claiming role-separated execution.',
|
|
823
|
+
),
|
|
824
|
+
makeCheck(
|
|
825
|
+
'CP15',
|
|
826
|
+
'state and evidence repair actions are available',
|
|
827
|
+
stateRepair.summary.status !== 'repair-required',
|
|
828
|
+
stateRepair.summary.actions
|
|
829
|
+
? `${stateRepair.summary.status}; ${stateRepair.summary.actions} repair action(s)`
|
|
830
|
+
: 'clean',
|
|
831
|
+
stateRepair.summary.status === 'repair-required' ? 'hard' : 'soft',
|
|
832
|
+
stateRepair.summary.status === 'clean'
|
|
833
|
+
? ''
|
|
834
|
+
: 'Run /gse repair or inspect compactState.stateRepair.repairActions before implementation.',
|
|
835
|
+
),
|
|
836
|
+
makeCheck(
|
|
837
|
+
'CP16',
|
|
838
|
+
'learning promotion candidates are classified',
|
|
839
|
+
learningPromotion.summary.status !== 'failed',
|
|
840
|
+
learningPromotion.exists
|
|
841
|
+
? `${learningPromotion.summary.promoted} promoted candidate(s); ${learningPromotion.summary.guardCandidates} guard candidate(s); ${learningPromotion.summary.scriptCandidates} script/skill candidate(s)`
|
|
842
|
+
: 'missing .gse/learnings.md',
|
|
843
|
+
'soft',
|
|
844
|
+
learningPromotion.exists
|
|
845
|
+
? 'Run /gse learn --promote --execute after reviewing candidate-only output.'
|
|
846
|
+
: 'Record reusable lessons with /gse learn before expecting promotion candidates.',
|
|
847
|
+
),
|
|
848
|
+
makeCheck(
|
|
849
|
+
'CP17',
|
|
850
|
+
'learning promotion drift is audited',
|
|
851
|
+
learningDrift.summary.status !== 'failed',
|
|
852
|
+
`${learningDrift.summary.enforced}/${learningDrift.summary.candidates} promoted candidate(s) enforced; ${learningDrift.summary.highUnenforced} high-severity drift(s)`,
|
|
853
|
+
'soft',
|
|
854
|
+
learningDrift.summary.highUnenforced
|
|
855
|
+
? 'Promote unenforced learning candidates into guards, quality gates, continue/close checks, or focused audit scripts.'
|
|
856
|
+
: 'Keep learning candidates mapped to executable controls as they are promoted.',
|
|
857
|
+
),
|
|
858
|
+
makeCheck(
|
|
859
|
+
'CP18',
|
|
860
|
+
'host capability records are audited',
|
|
861
|
+
hostCapabilities.status !== 'failed',
|
|
862
|
+
hostCapabilities.exists
|
|
863
|
+
? `${hostCapabilities.summary.total} capability row(s); verified=${hostCapabilities.summary.verified.length}; unknown=${hostCapabilities.summary.unknown.length}; external-required=${hostCapabilities.summary.externalRequired.length}`
|
|
864
|
+
: 'missing .gse/host-capabilities.md',
|
|
865
|
+
'soft',
|
|
866
|
+
hostCapabilities.exists
|
|
867
|
+
? 'Update .gse/host-capabilities.md when host/browser/MCP/LSP/subagent/CI evidence changes.'
|
|
868
|
+
: 'Add .gse/host-capabilities.md or update the GSE scaffold before making host/tool capability claims.',
|
|
869
|
+
),
|
|
870
|
+
makeCheck(
|
|
871
|
+
'CP19',
|
|
872
|
+
'optional tool fallback policy is auditable',
|
|
873
|
+
toolFallbackPolicy.summary.status !== 'failed',
|
|
874
|
+
`${toolFallbackPolicy.summary.passed}/${toolFallbackPolicy.summary.total} fallback policy check(s)`,
|
|
875
|
+
'soft',
|
|
876
|
+
toolFallbackPolicy.summary.status === 'failed'
|
|
877
|
+
? 'Restore markdown fallback and claim boundaries for optional tools before continuing.'
|
|
878
|
+
: 'Keep optional tools non-blocking and evidence-gated in host and router docs.',
|
|
879
|
+
),
|
|
880
|
+
makeCheck(
|
|
881
|
+
'CP20',
|
|
882
|
+
'latest maintenance snapshot is available',
|
|
883
|
+
maintenanceSnapshot.exists && maintenanceSnapshot.status === 'passed',
|
|
884
|
+
maintenanceSnapshot.exists
|
|
885
|
+
? `${maintenanceSnapshot.status}; installedSync=${maintenanceSnapshot.installedSync}; releaseBundle=${maintenanceSnapshot.releaseBundleFreshness}; mode=${maintenanceSnapshot.installedSyncMode}`
|
|
886
|
+
: 'missing .gse/maintenance/latest-maintenance-snapshot.json',
|
|
887
|
+
'soft',
|
|
888
|
+
maintenanceSnapshot.exists
|
|
889
|
+
? 'Regenerate /gse maintenance after capability upgrades or before release-sensitive claims.'
|
|
890
|
+
: 'Run /gse maintenance --installed-root <installed-skill-dir> --execute when recurring final-form freshness must be proven.',
|
|
891
|
+
),
|
|
892
|
+
makeCheck(
|
|
893
|
+
'CP21',
|
|
894
|
+
'session sync adoption boundary is explicit',
|
|
895
|
+
sessionSyncBoundary.valid,
|
|
896
|
+
sessionSyncBoundary.exists
|
|
897
|
+
? `${sessionSyncBoundary.boundary}; installedSync=${sessionSyncBoundary.installedSyncRecorded}; adoptionProven=${sessionSyncBoundary.adoptionProven}`
|
|
898
|
+
: 'no session sync records claimed',
|
|
899
|
+
sessionSyncBoundary.valid ? 'soft' : 'hard',
|
|
900
|
+
sessionSyncBoundary.valid
|
|
901
|
+
? 'Do not infer target-session adoption from installed-copy sync or notification records.'
|
|
902
|
+
: 'Repair .gse/session-sync.jsonl before relying on sync records.',
|
|
903
|
+
),
|
|
904
|
+
]
|
|
905
|
+
|
|
906
|
+
const projectName = cleanInlineValue(
|
|
907
|
+
state?.projectName ||
|
|
908
|
+
firstMatch(profileText, /Product\/system name:\s*([^\n]+)/i) ||
|
|
909
|
+
path.basename(resolvedTarget),
|
|
910
|
+
)
|
|
911
|
+
const residualRisks = Array.isArray(state?.residualRisks) ? state.residualRisks : []
|
|
912
|
+
const archivedRisks = Array.isArray(state?.riskArchive) ? state.riskArchive : []
|
|
913
|
+
const topRisks = residualRisks.slice(0, 3)
|
|
914
|
+
const hiddenActiveRiskCount = Math.max(0, residualRisks.length - topRisks.length)
|
|
915
|
+
const archivedRiskCount = archivedRisks.length
|
|
916
|
+
const totalRiskCount = residualRisks.length + archivedRisks.length
|
|
917
|
+
const blockedGates = []
|
|
918
|
+
|
|
919
|
+
const targetHasPublicAcceptanceDoctor =
|
|
920
|
+
fs.existsSync(path.join(resolvedTarget, 'scripts', 'audit-public-acceptance-readiness.mjs')) &&
|
|
921
|
+
fs.existsSync(path.join(resolvedTarget, 'references', 'final-readiness.md')) &&
|
|
922
|
+
fs.existsSync(path.join(root, 'scripts', 'audit-public-acceptance-readiness.mjs'))
|
|
923
|
+
|
|
924
|
+
let ownerExternalGateSummary = null
|
|
925
|
+
if (targetHasPublicAcceptanceDoctor) {
|
|
926
|
+
const doctor = runNode('audit-public-acceptance-readiness.mjs', ['--root', resolvedTarget, '--json'])
|
|
927
|
+
ownerExternalGateSummary = {
|
|
928
|
+
status: doctor.data?.summary?.status ?? 'unknown',
|
|
929
|
+
publicAccepted: doctor.data?.summary?.publicAccepted ?? 'unknown',
|
|
930
|
+
pendingGates: doctor.data?.summary?.pendingGates ?? 0,
|
|
931
|
+
command: doctor.command,
|
|
932
|
+
}
|
|
933
|
+
for (const gate of doctor.data?.pendingGates ?? []) {
|
|
934
|
+
blockedGates.push({
|
|
935
|
+
area: gate.area,
|
|
936
|
+
status: gate.status,
|
|
937
|
+
owner: gate.owner,
|
|
938
|
+
requiredEvidence: gate.requiredEvidence,
|
|
939
|
+
gateType: 'owner-external',
|
|
940
|
+
blockingScope: 'public-or-host-claim',
|
|
941
|
+
})
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const failedHardChecks = checks.filter((item) => item.status === 'failed' && item.severity === 'hard')
|
|
946
|
+
const warningChecks = checks.filter((item) => item.status === 'warning')
|
|
947
|
+
const preflightStatus = failedHardChecks.length > 0 ? 'failed' : blockedGates.length > 0 || warningChecks.length > 0 ? 'warning' : 'passed'
|
|
948
|
+
const latestEvidence = evidenceIndex.records.at(-1) ?? null
|
|
949
|
+
const latestEvidenceLevel = latestEvidence?.evidenceLevel ?? null
|
|
950
|
+
const completionPlan = buildCompletionPlan(resolvedTarget, state, maintenanceSnapshot)
|
|
951
|
+
const gateTaxonomy = buildGateTaxonomy(blockedGates)
|
|
952
|
+
const nextSliceMode = buildNextSliceMode(state)
|
|
953
|
+
const nextSliceCandidates = buildNextSliceCandidates(resolvedTarget, state, nextSliceMode)
|
|
954
|
+
const nextChecks = [
|
|
955
|
+
'node scripts/run-gse-command.mjs --target <project-root> --command "/gse doctor" --json',
|
|
956
|
+
...completionPlan.requiredCloseCommands,
|
|
957
|
+
...completionPlan.activeCloseCommands,
|
|
958
|
+
]
|
|
959
|
+
|
|
960
|
+
const compactState = {
|
|
961
|
+
projectName,
|
|
962
|
+
mode: state?.mode ?? 'unknown',
|
|
963
|
+
phase: state?.phase ?? 'unknown',
|
|
964
|
+
canonicalPlan,
|
|
965
|
+
currentSlice: {
|
|
966
|
+
id: state?.currentSlice?.id ?? null,
|
|
967
|
+
outcome: state?.currentSlice?.outcome || firstMatch(goalMapText, /Active slice:\s*([^\n]+)/i) || 'Read goal map and select the next verifiable slice.',
|
|
968
|
+
status: state?.currentSlice?.status ?? 'unknown',
|
|
969
|
+
nextAction: state?.currentSlice?.nextAction || firstMatch(goalMapText, /Next action:\s*([^\n]+)/i) || 'Continue with the smallest verifiable GSE slice.',
|
|
970
|
+
},
|
|
971
|
+
topRisks,
|
|
972
|
+
riskCount: residualRisks.length,
|
|
973
|
+
activeRiskCount: residualRisks.length,
|
|
974
|
+
hiddenActiveRiskCount,
|
|
975
|
+
archivedRiskCount,
|
|
976
|
+
totalRiskCount,
|
|
977
|
+
blockedGates,
|
|
978
|
+
gateTaxonomy,
|
|
979
|
+
nextSliceMode,
|
|
980
|
+
nextSliceCandidates,
|
|
981
|
+
nextChecks,
|
|
982
|
+
completionPlan,
|
|
983
|
+
toolStatuses: state?.toolStatuses ?? {},
|
|
984
|
+
latestEvidence,
|
|
985
|
+
latestEvidenceLevel,
|
|
986
|
+
evidenceLevels: {
|
|
987
|
+
allowed: evidenceLevelAnalysis.allowed,
|
|
988
|
+
recordsWithLevel: evidenceLevelAnalysis.recordsWithLevel,
|
|
989
|
+
missingLevel: evidenceLevelAnalysis.missingLevel.length,
|
|
990
|
+
invalidLevel: evidenceLevelAnalysis.invalidLevel,
|
|
991
|
+
downgraded: evidenceLevelAnalysis.downgraded,
|
|
992
|
+
},
|
|
993
|
+
evidenceReviewQueue: {
|
|
994
|
+
totalRecords: evidenceReviewQueue.counts.totalRecords,
|
|
995
|
+
queued: evidenceReviewQueue.counts.queued,
|
|
996
|
+
needsReview: evidenceReviewQueue.counts.needsReview,
|
|
997
|
+
safeResult: evidenceReviewQueue.counts.safeResult,
|
|
998
|
+
eligibleForStrongerReview: evidenceReviewQueue.counts.eligibleForStrongerReview,
|
|
999
|
+
externalRequired: evidenceReviewQueue.counts.externalRequired,
|
|
1000
|
+
missingEvidenceFile: evidenceReviewQueue.counts.missingEvidenceFile,
|
|
1001
|
+
items: evidenceReviewQueue.queue.slice(0, 5),
|
|
1002
|
+
},
|
|
1003
|
+
projectGuards: {
|
|
1004
|
+
status: projectGuards.status,
|
|
1005
|
+
activeCount: projectGuards.active.length,
|
|
1006
|
+
guards: projectGuards.active.map((guard) => ({
|
|
1007
|
+
id: guard.id,
|
|
1008
|
+
severity: guard.severity,
|
|
1009
|
+
trigger: guard.trigger,
|
|
1010
|
+
check: guard.check,
|
|
1011
|
+
})),
|
|
1012
|
+
missingDefaultIds: projectGuards.summary.missingDefaultIds,
|
|
1013
|
+
},
|
|
1014
|
+
roleFallback: {
|
|
1015
|
+
status: roleFallback.status,
|
|
1016
|
+
exists: roleFallback.exists,
|
|
1017
|
+
total: roleFallback.summary.total,
|
|
1018
|
+
requiredRoles: roleFallback.summary.requiredRoles,
|
|
1019
|
+
missingRoles: roleFallback.summary.missingRoles,
|
|
1020
|
+
sequentialFallbackRoles: roleFallback.summary.sequentialFallbackRoles,
|
|
1021
|
+
fakeDelegationRisk: roleFallback.summary.fakeDelegationRisk,
|
|
1022
|
+
},
|
|
1023
|
+
stateRepair: {
|
|
1024
|
+
status: stateRepair.summary.status,
|
|
1025
|
+
hard: stateRepair.summary.hard,
|
|
1026
|
+
warnings: stateRepair.summary.warnings,
|
|
1027
|
+
repairActions: stateRepair.repairActions,
|
|
1028
|
+
},
|
|
1029
|
+
learningPromotion: {
|
|
1030
|
+
status: learningPromotion.summary.status,
|
|
1031
|
+
entries: learningPromotion.summary.entries,
|
|
1032
|
+
uniqueLessons: learningPromotion.summary.uniqueLessons,
|
|
1033
|
+
promoted: learningPromotion.summary.promoted,
|
|
1034
|
+
guardCandidates: learningPromotion.summary.guardCandidates,
|
|
1035
|
+
scriptCandidates: learningPromotion.summary.scriptCandidates,
|
|
1036
|
+
topCandidates: learningPromotion.promotions.slice(0, 5).map((item) => ({
|
|
1037
|
+
id: item.id,
|
|
1038
|
+
category: item.category,
|
|
1039
|
+
severity: item.severity,
|
|
1040
|
+
count: item.count,
|
|
1041
|
+
promotionLevel: item.promotionLevel,
|
|
1042
|
+
promotionTarget: item.promotionTarget,
|
|
1043
|
+
})),
|
|
1044
|
+
},
|
|
1045
|
+
learningDrift: {
|
|
1046
|
+
status: learningDrift.summary.status,
|
|
1047
|
+
candidates: learningDrift.summary.candidates,
|
|
1048
|
+
enforced: learningDrift.summary.enforced,
|
|
1049
|
+
unenforced: learningDrift.summary.unenforced,
|
|
1050
|
+
highUnenforced: learningDrift.summary.highUnenforced,
|
|
1051
|
+
unenforcedCandidates: learningDrift.unenforced.slice(0, 5).map((item) => ({
|
|
1052
|
+
id: item.candidateId,
|
|
1053
|
+
category: item.category,
|
|
1054
|
+
severity: item.severity,
|
|
1055
|
+
promotionLevel: item.promotionLevel,
|
|
1056
|
+
recommendation: item.recommendation,
|
|
1057
|
+
})),
|
|
1058
|
+
},
|
|
1059
|
+
hostCapabilities: {
|
|
1060
|
+
status: hostCapabilities.status,
|
|
1061
|
+
exists: hostCapabilities.exists,
|
|
1062
|
+
total: hostCapabilities.summary.total,
|
|
1063
|
+
verified: hostCapabilities.summary.verified,
|
|
1064
|
+
documented: hostCapabilities.summary.documented,
|
|
1065
|
+
unknown: hostCapabilities.summary.unknown,
|
|
1066
|
+
unavailable: hostCapabilities.summary.unavailable,
|
|
1067
|
+
externalRequired: hostCapabilities.summary.externalRequired,
|
|
1068
|
+
missingRequired: hostCapabilities.summary.missingRequired,
|
|
1069
|
+
nativeSlashOverclaim: hostCapabilities.summary.nativeSlashOverclaim,
|
|
1070
|
+
},
|
|
1071
|
+
toolFallbackPolicy: {
|
|
1072
|
+
status: toolFallbackPolicy.summary.status,
|
|
1073
|
+
checks: toolFallbackPolicy.summary.passed + '/' + toolFallbackPolicy.summary.total,
|
|
1074
|
+
optionalToolFallbackPolicy: toolFallbackPolicy.workflows.optionalToolFallbackPolicy,
|
|
1075
|
+
},
|
|
1076
|
+
maintenanceSnapshot: {
|
|
1077
|
+
exists: maintenanceSnapshot.exists,
|
|
1078
|
+
status: maintenanceSnapshot.status,
|
|
1079
|
+
generatedAt: maintenanceSnapshot.generatedAt,
|
|
1080
|
+
summary: maintenanceSnapshot.summary,
|
|
1081
|
+
installedSync: maintenanceSnapshot.installedSync,
|
|
1082
|
+
releaseBundleFreshness: maintenanceSnapshot.releaseBundleFreshness,
|
|
1083
|
+
installedSyncMode: maintenanceSnapshot.installedSyncMode,
|
|
1084
|
+
},
|
|
1085
|
+
sessionSyncBoundary: {
|
|
1086
|
+
exists: sessionSyncBoundary.exists,
|
|
1087
|
+
valid: sessionSyncBoundary.valid,
|
|
1088
|
+
boundary: sessionSyncBoundary.boundary,
|
|
1089
|
+
totalRecords: sessionSyncBoundary.totalRecords,
|
|
1090
|
+
statusCounts: sessionSyncBoundary.statusCounts,
|
|
1091
|
+
installedSyncRecorded: sessionSyncBoundary.installedSyncRecorded,
|
|
1092
|
+
adoptionProven: sessionSyncBoundary.adoptionProven,
|
|
1093
|
+
latestInstalledSync: sessionSyncBoundary.latestInstalledSync,
|
|
1094
|
+
latestThreadOutcomes: sessionSyncBoundary.latestThreadOutcomes,
|
|
1095
|
+
latestRecords: sessionSyncBoundary.latestRecords,
|
|
1096
|
+
limits: sessionSyncBoundary.limits,
|
|
1097
|
+
},
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
const prompt = buildShortPrompt({ projectName, resolvedTarget, preflightStatus, compactState, blockedGates, failedHardChecks })
|
|
1101
|
+
|
|
1102
|
+
return {
|
|
1103
|
+
target: resolvedTarget,
|
|
1104
|
+
generatedAt: new Date().toISOString(),
|
|
1105
|
+
summary: {
|
|
1106
|
+
status: preflightStatus,
|
|
1107
|
+
failedHardChecks: failedHardChecks.length,
|
|
1108
|
+
warnings: warningChecks.length,
|
|
1109
|
+
blockedGates: blockedGates.length,
|
|
1110
|
+
riskCount: residualRisks.length,
|
|
1111
|
+
activeRiskCount: residualRisks.length,
|
|
1112
|
+
archivedRiskCount,
|
|
1113
|
+
totalRiskCount,
|
|
1114
|
+
topRiskCount: topRisks.length,
|
|
1115
|
+
},
|
|
1116
|
+
preflight: {
|
|
1117
|
+
status: preflightStatus,
|
|
1118
|
+
checks,
|
|
1119
|
+
failures: failedHardChecks,
|
|
1120
|
+
warnings: warningChecks,
|
|
1121
|
+
},
|
|
1122
|
+
compactState,
|
|
1123
|
+
ownerExternalGateSummary,
|
|
1124
|
+
prompt,
|
|
1125
|
+
limits: [
|
|
1126
|
+
'This packet is the portable /gse continue preflight, not host-native slash-command proof.',
|
|
1127
|
+
'Owner/external records affect only public release, marketplace, registry, or host-specific claims; they are not GSE core workflow blockers unless project policy says so.',
|
|
1128
|
+
'Bad state or evidence index is a hard preflight failure and must be repaired before implementation starts.',
|
|
1129
|
+
],
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
function renderMarkdown(report) {
|
|
1134
|
+
const lines = []
|
|
1135
|
+
lines.push('# GSE Continue Packet')
|
|
1136
|
+
lines.push('')
|
|
1137
|
+
lines.push('Generated: ' + report.generatedAt)
|
|
1138
|
+
lines.push('Target: ' + report.target)
|
|
1139
|
+
lines.push('')
|
|
1140
|
+
lines.push('## Summary')
|
|
1141
|
+
lines.push('')
|
|
1142
|
+
lines.push('- Status: ' + report.summary.status)
|
|
1143
|
+
lines.push('- Project: ' + report.compactState.projectName)
|
|
1144
|
+
lines.push('- Phase: ' + report.compactState.phase)
|
|
1145
|
+
lines.push('- Current slice: ' + report.compactState.currentSlice.outcome)
|
|
1146
|
+
lines.push('- Next action: ' + report.compactState.currentSlice.nextAction)
|
|
1147
|
+
lines.push('- Risks: ' + report.summary.topRiskCount + ' shown / ' + report.summary.activeRiskCount + ' active (' + report.summary.archivedRiskCount + ' archived)')
|
|
1148
|
+
lines.push('- Pending public/host claim evidence: ' + report.summary.blockedGates)
|
|
1149
|
+
lines.push('- Gate taxonomy: core gates block default continuation; release and host-adapter gates block only their specific claims unless project policy promotes them.')
|
|
1150
|
+
lines.push('- Active project guards: ' + report.compactState.projectGuards.activeCount)
|
|
1151
|
+
lines.push('- Latest evidence level: ' + (report.compactState.latestEvidenceLevel || 'missing'))
|
|
1152
|
+
lines.push('- Evidence level downgrades: ' + report.compactState.evidenceLevels.downgraded.length)
|
|
1153
|
+
lines.push('- UI/browser proof boundary: `verified-component` is not `verified-browser`; screenshot/browser-backed claims require `verified-browser` evidence.')
|
|
1154
|
+
lines.push('- Role fallback packets: ' + report.compactState.roleFallback.total + ' (' + report.compactState.roleFallback.status + ')')
|
|
1155
|
+
lines.push('- State repair: ' + report.compactState.stateRepair.status + ' (' + report.compactState.stateRepair.repairActions.length + ' action(s))')
|
|
1156
|
+
lines.push('- Learning promotion: ' + report.compactState.learningPromotion.status + ' (' + report.compactState.learningPromotion.promoted + ' candidate(s))')
|
|
1157
|
+
lines.push('- Learning drift: ' + report.compactState.learningDrift.status + ' (' + report.compactState.learningDrift.enforced + '/' + report.compactState.learningDrift.candidates + ' enforced)')
|
|
1158
|
+
lines.push('- Host capabilities: ' + report.compactState.hostCapabilities.status + ' (' + report.compactState.hostCapabilities.total + ' row(s))')
|
|
1159
|
+
lines.push('- Maintenance snapshot: ' + report.compactState.maintenanceSnapshot.status + ' (' + report.compactState.maintenanceSnapshot.installedSyncMode + ')')
|
|
1160
|
+
lines.push('- Session sync boundary: ' + report.compactState.sessionSyncBoundary.boundary + ' (adoptionProven=' + report.compactState.sessionSyncBoundary.adoptionProven + ')')
|
|
1161
|
+
lines.push('- Completion plan: ' + report.compactState.completionPlan.requiredCloseCommands.length + ' required command(s), ' + report.compactState.completionPlan.activeCloseCommands.length + ' active conditional command(s)')
|
|
1162
|
+
if (report.compactState.completionPlan.ignoredGeneratedPathCount > 0) {
|
|
1163
|
+
lines.push('- Ignored generated/noisy paths: ' + report.compactState.completionPlan.ignoredGeneratedPathCount)
|
|
1164
|
+
}
|
|
1165
|
+
lines.push('')
|
|
1166
|
+
lines.push('## Preflight')
|
|
1167
|
+
lines.push('')
|
|
1168
|
+
for (const item of report.preflight.checks) {
|
|
1169
|
+
const marker = item.status === 'passed' ? '[x]' : item.status === 'warning' ? '[!]' : '[ ]'
|
|
1170
|
+
lines.push('- ' + marker + ' ' + item.id + ' ' + item.label + ': ' + item.evidence)
|
|
1171
|
+
}
|
|
1172
|
+
lines.push('')
|
|
1173
|
+
lines.push('## Completion Plan')
|
|
1174
|
+
lines.push('')
|
|
1175
|
+
lines.push('Required steps:')
|
|
1176
|
+
for (const step of report.compactState.completionPlan.requiredSteps) {
|
|
1177
|
+
lines.push('- ' + step)
|
|
1178
|
+
}
|
|
1179
|
+
lines.push('')
|
|
1180
|
+
lines.push('Required commands:')
|
|
1181
|
+
for (const command of report.compactState.completionPlan.requiredCloseCommands) {
|
|
1182
|
+
lines.push('- `' + command + '`')
|
|
1183
|
+
}
|
|
1184
|
+
const activeConditional = report.compactState.completionPlan.conditionalCloseCommands.filter((item) => item.active)
|
|
1185
|
+
if (activeConditional.length) {
|
|
1186
|
+
lines.push('')
|
|
1187
|
+
lines.push('Active conditional commands:')
|
|
1188
|
+
for (const item of activeConditional) {
|
|
1189
|
+
lines.push('- `' + item.command + '` - ' + item.when)
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (report.compactState.completionPlan.ignoredGeneratedPathCount > 0) {
|
|
1193
|
+
lines.push('')
|
|
1194
|
+
lines.push('Ignored generated/noisy paths:')
|
|
1195
|
+
for (const item of report.compactState.completionPlan.ignoredGeneratedPaths) {
|
|
1196
|
+
lines.push('- `' + item + '`')
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
lines.push('')
|
|
1200
|
+
lines.push('## Prompt')
|
|
1201
|
+
lines.push('')
|
|
1202
|
+
lines.push('```text')
|
|
1203
|
+
lines.push(report.prompt)
|
|
1204
|
+
lines.push('```')
|
|
1205
|
+
return lines.join('\n') + '\n'
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const target = selfTest ? createFixture({ invalidEvidence: args.includes('--invalid-evidence-fixture') }) : targetArg
|
|
1209
|
+
const report = buildContinuePacket(target)
|
|
1210
|
+
|
|
1211
|
+
if (jsonOnly) console.log(JSON.stringify(report, null, 2))
|
|
1212
|
+
else console.log(renderMarkdown(report))
|
|
1213
|
+
|
|
1214
|
+
if (report.preflight.status === 'failed') process.exit(1)
|