@devtrack-solution/codesdd 1.2.3 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/.sdd/skills/curated/devtrack-api/SKILL.md +98 -12
  2. package/.sdd/skills/curated/devtrack-api/agents/claude-code.yaml +10 -0
  3. package/.sdd/skills/curated/devtrack-api/agents/codex.yaml +10 -0
  4. package/.sdd/skills/curated/devtrack-api/agents/cursor.yaml +10 -0
  5. package/.sdd/skills/curated/devtrack-api/agents/gemini.yaml +10 -0
  6. package/.sdd/skills/curated/devtrack-api/agents/kimi.yaml +10 -0
  7. package/.sdd/skills/curated/devtrack-api/agents/openai.yaml +5 -3
  8. package/.sdd/skills/curated/devtrack-api/agents/opencode.yaml +12 -0
  9. package/.sdd/skills/curated/devtrack-api/references/application-presentation.md +61 -5
  10. package/.sdd/skills/curated/devtrack-api/references/consumer-sync-policy.md +15 -3
  11. package/.sdd/skills/curated/devtrack-api/references/contract-pack.yaml +1951 -0
  12. package/.sdd/skills/curated/devtrack-api/references/domain-modeling.md +16 -14
  13. package/.sdd/skills/curated/devtrack-api/references/field-validation-protocol.md +40 -0
  14. package/.sdd/skills/curated/devtrack-api/references/foundation-layout.md +19 -2
  15. package/.sdd/skills/curated/devtrack-api/references/generated-artifact-invalidation.md +97 -0
  16. package/.sdd/skills/curated/devtrack-api/references/implementation-checklist.md +30 -1
  17. package/.sdd/skills/curated/devtrack-api/references/portable-agent-contract.md +42 -0
  18. package/.sdd/skills/curated/devtrack-api/references/testing-validation.md +22 -1
  19. package/.sdd/skills/curated/devtrack-api/references/typeorm-infrastructure.md +9 -7
  20. package/README.md +280 -29
  21. package/dist/applications/sdd/index.d.ts +16 -0
  22. package/dist/applications/sdd/index.js +16 -0
  23. package/dist/cli/program.js +180 -11
  24. package/dist/commands/config.js +197 -10
  25. package/dist/commands/sdd/execution.js +408 -16
  26. package/dist/commands/sdd/plugin.js +5 -0
  27. package/dist/commands/sdd/shared.d.ts +1 -0
  28. package/dist/commands/sdd/shared.js +10 -0
  29. package/dist/commands/sdd.js +157 -7
  30. package/dist/core/cli/command-matrix.d.ts +18 -0
  31. package/dist/core/cli/command-matrix.js +157 -0
  32. package/dist/core/cli-command-quality.js +11 -0
  33. package/dist/core/completions/command-registry.js +45 -0
  34. package/dist/core/config-schema.d.ts +31 -1
  35. package/dist/core/config-schema.js +79 -5
  36. package/dist/core/config.d.ts +1 -0
  37. package/dist/core/config.js +11 -0
  38. package/dist/core/global-config.d.ts +29 -0
  39. package/dist/core/init.d.ts +2 -2
  40. package/dist/core/init.js +13 -14
  41. package/dist/core/sdd/agent-binding.d.ts +19 -19
  42. package/dist/core/sdd/agent-runtime-contract.d.ts +204 -0
  43. package/dist/core/sdd/agent-runtime-contract.js +200 -0
  44. package/dist/core/sdd/allocator-recovery.d.ts +14 -0
  45. package/dist/core/sdd/allocator-recovery.js +30 -0
  46. package/dist/core/sdd/allocator-security.d.ts +18 -0
  47. package/dist/core/sdd/allocator-security.js +36 -0
  48. package/dist/core/sdd/api-foundation-baseline.d.ts +111 -0
  49. package/dist/core/sdd/api-foundation-baseline.js +151 -0
  50. package/dist/core/sdd/api-foundation-parity.d.ts +114 -0
  51. package/dist/core/sdd/api-foundation-parity.js +131 -0
  52. package/dist/core/sdd/api-profile-catalog.d.ts +36 -0
  53. package/dist/core/sdd/api-profile-catalog.js +132 -0
  54. package/dist/core/sdd/api-profile-dry-run-projection.d.ts +93 -0
  55. package/dist/core/sdd/api-profile-dry-run-projection.js +370 -0
  56. package/dist/core/sdd/api-profile-recipes.d.ts +82 -0
  57. package/dist/core/sdd/api-profile-recipes.js +484 -0
  58. package/dist/core/sdd/artifact-id-allocator.d.ts +368 -0
  59. package/dist/core/sdd/artifact-id-allocator.js +510 -0
  60. package/dist/core/sdd/check.d.ts +52 -1
  61. package/dist/core/sdd/check.js +326 -11
  62. package/dist/core/sdd/coordination/coordination-adapters.d.ts +15 -8
  63. package/dist/core/sdd/coordination/coordination-adapters.js +43 -15
  64. package/dist/core/sdd/coordination/index.d.ts +1 -0
  65. package/dist/core/sdd/coordination/index.js +1 -0
  66. package/dist/core/sdd/coordination/redis-runtime.d.ts +131 -0
  67. package/dist/core/sdd/coordination/redis-runtime.js +698 -0
  68. package/dist/core/sdd/deepagent-contracts.d.ts +99 -5
  69. package/dist/core/sdd/deepagent-contracts.js +62 -0
  70. package/dist/core/sdd/deepagents/reversa-subagents.d.ts +3 -3
  71. package/dist/core/sdd/default-bootstrap-files.d.ts +2 -2
  72. package/dist/core/sdd/default-bootstrap-files.js +14 -10
  73. package/dist/core/sdd/default-skills.js +115 -9
  74. package/dist/core/sdd/devtrack-api-appliance.d.ts +42 -1
  75. package/dist/core/sdd/devtrack-api-appliance.js +159 -32
  76. package/dist/core/sdd/devtrack-api-architecture.d.ts +16 -0
  77. package/dist/core/sdd/devtrack-api-architecture.js +86 -0
  78. package/dist/core/sdd/docs-sync.js +24 -18
  79. package/dist/core/sdd/domain/capability-diff.d.ts +63 -0
  80. package/dist/core/sdd/domain/capability-diff.js +200 -0
  81. package/dist/core/sdd/domain/change-safety-guardrails.d.ts +74 -0
  82. package/dist/core/sdd/domain/change-safety-guardrails.js +333 -0
  83. package/dist/core/sdd/domain/semantic-intent-classifier.d.ts +29 -0
  84. package/dist/core/sdd/domain/semantic-intent-classifier.js +117 -0
  85. package/dist/core/sdd/enterprise-mutating-command-gate.d.ts +27 -0
  86. package/dist/core/sdd/enterprise-mutating-command-gate.js +104 -0
  87. package/dist/core/sdd/enterprise-provenance-gates.d.ts +20 -0
  88. package/dist/core/sdd/enterprise-provenance-gates.js +63 -0
  89. package/dist/core/sdd/enterprise-provisioning-policy.d.ts +26 -0
  90. package/dist/core/sdd/enterprise-provisioning-policy.js +104 -0
  91. package/dist/core/sdd/foundation-artifact-map-validator.d.ts +16 -0
  92. package/dist/core/sdd/foundation-artifact-map-validator.js +71 -0
  93. package/dist/core/sdd/foundation-layer-manifest.d.ts +24 -0
  94. package/dist/core/sdd/foundation-layer-manifest.js +117 -0
  95. package/dist/core/sdd/governance-schemas.d.ts +2 -2
  96. package/dist/core/sdd/governance-schemas.js +11 -2
  97. package/dist/core/sdd/intent-guard.d.ts +22 -0
  98. package/dist/core/sdd/intent-guard.js +67 -0
  99. package/dist/core/sdd/json-schema.js +13 -1
  100. package/dist/core/sdd/legacy-operations.js +169 -5
  101. package/dist/core/sdd/migrate-workspace.js +39 -0
  102. package/dist/core/sdd/package-security-gates.d.ts +21 -0
  103. package/dist/core/sdd/package-security-gates.js +121 -0
  104. package/dist/core/sdd/package-structure-gate.d.ts +85 -3
  105. package/dist/core/sdd/package-structure-gate.js +384 -11
  106. package/dist/core/sdd/parallel-feat-automation.d.ts +185 -7
  107. package/dist/core/sdd/parallel-feat-automation.js +212 -0
  108. package/dist/core/sdd/plugin-broker.d.ts +223 -4
  109. package/dist/core/sdd/plugin-broker.js +10 -0
  110. package/dist/core/sdd/plugin-cli.d.ts +30 -0
  111. package/dist/core/sdd/plugin-cli.js +70 -3
  112. package/dist/core/sdd/plugin-evidence.d.ts +73 -0
  113. package/dist/core/sdd/plugin-manifest.d.ts +69 -1
  114. package/dist/core/sdd/plugin-manifest.js +10 -0
  115. package/dist/core/sdd/plugin-policy-pack.d.ts +1 -1
  116. package/dist/core/sdd/plugin-policy.js +6 -1
  117. package/dist/core/sdd/plugin-registry.d.ts +138 -2
  118. package/dist/core/sdd/plugin-sdk-contract.d.ts +363 -0
  119. package/dist/core/sdd/plugin-sdk-contract.js +268 -0
  120. package/dist/core/sdd/plugin-skill-binding.d.ts +1 -1
  121. package/dist/core/sdd/quality-validation.d.ts +89 -16
  122. package/dist/core/sdd/release-readiness.d.ts +68 -0
  123. package/dist/core/sdd/release-readiness.js +767 -0
  124. package/dist/core/sdd/reversa-architecture-extractor.d.ts +13 -0
  125. package/dist/core/sdd/reversa-architecture-extractor.js +89 -0
  126. package/dist/core/sdd/reversa-artifact-writer.d.ts +18 -0
  127. package/dist/core/sdd/reversa-artifact-writer.js +40 -0
  128. package/dist/core/sdd/reversa-command-policy.d.ts +136 -0
  129. package/dist/core/sdd/reversa-command-policy.js +361 -0
  130. package/dist/core/sdd/reversa-data-extractor.d.ts +11 -0
  131. package/dist/core/sdd/reversa-data-extractor.js +73 -0
  132. package/dist/core/sdd/reversa-equivalence.d.ts +20 -0
  133. package/dist/core/sdd/reversa-equivalence.js +34 -0
  134. package/dist/core/sdd/reversa-evidence.d.ts +298 -0
  135. package/dist/core/sdd/reversa-evidence.js +118 -0
  136. package/dist/core/sdd/reversa-reconstruction.d.ts +29 -0
  137. package/dist/core/sdd/reversa-reconstruction.js +32 -0
  138. package/dist/core/sdd/reversa-rules-extractor.d.ts +12 -0
  139. package/dist/core/sdd/reversa-rules-extractor.js +86 -0
  140. package/dist/core/sdd/reversa-source-safety.d.ts +19 -0
  141. package/dist/core/sdd/reversa-source-safety.js +105 -0
  142. package/dist/core/sdd/reversa-surface-scout.d.ts +13 -0
  143. package/dist/core/sdd/reversa-surface-scout.js +85 -0
  144. package/dist/core/sdd/reversa-ux-mapper.d.ts +11 -0
  145. package/dist/core/sdd/reversa-ux-mapper.js +73 -0
  146. package/dist/core/sdd/runtime-boundary-contract.d.ts +45 -0
  147. package/dist/core/sdd/runtime-boundary-contract.js +90 -0
  148. package/dist/core/sdd/sdk-agent-plugin-quality-gates.d.ts +150 -0
  149. package/dist/core/sdd/sdk-agent-plugin-quality-gates.js +258 -0
  150. package/dist/core/sdd/services/agent-run.service.d.ts +38 -6
  151. package/dist/core/sdd/services/agent-run.service.js +73 -1
  152. package/dist/core/sdd/services/archive-quality-coherence.service.d.ts +17 -0
  153. package/dist/core/sdd/services/archive-quality-coherence.service.js +141 -0
  154. package/dist/core/sdd/services/capability-diff.service.d.ts +18 -0
  155. package/dist/core/sdd/services/capability-diff.service.js +26 -0
  156. package/dist/core/sdd/services/change-safety-preflight.service.d.ts +17 -0
  157. package/dist/core/sdd/services/change-safety-preflight.service.js +17 -0
  158. package/dist/core/sdd/services/context.service.d.ts +43 -340
  159. package/dist/core/sdd/services/context.service.js +323 -9
  160. package/dist/core/sdd/services/decide.service.js +1 -1
  161. package/dist/core/sdd/services/finalize.service.d.ts +27 -0
  162. package/dist/core/sdd/services/finalize.service.js +226 -18
  163. package/dist/core/sdd/services/frontend-impact.service.d.ts +1 -1
  164. package/dist/core/sdd/services/historical-quality-regression.service.d.ts +35 -0
  165. package/dist/core/sdd/services/historical-quality-regression.service.js +228 -0
  166. package/dist/core/sdd/services/ingest-deposito.service.js +1 -1
  167. package/dist/core/sdd/services/planning-execution-coherence.service.d.ts +45 -0
  168. package/dist/core/sdd/services/planning-execution-coherence.service.js +225 -0
  169. package/dist/core/sdd/services/semantic-intent-classifier.service.d.ts +6 -0
  170. package/dist/core/sdd/services/semantic-intent-classifier.service.js +7 -0
  171. package/dist/core/sdd/state.d.ts +1 -0
  172. package/dist/core/sdd/state.js +266 -34
  173. package/dist/core/sdd/store/sdd-stores.js +2 -2
  174. package/dist/core/sdd/structural-health.d.ts +13 -13
  175. package/dist/core/sdd/types.d.ts +30 -15
  176. package/dist/core/sdd/types.js +4 -0
  177. package/dist/core/sdd/views.js +17 -0
  178. package/dist/core/sdd/workspace-schemas.d.ts +428 -7
  179. package/dist/core/sdd/workspace-schemas.js +223 -70
  180. package/dist/core/shared/skill-generation.d.ts +2 -0
  181. package/dist/core/shared/skill-generation.js +19 -2
  182. package/dist/core/shared/tool-detection.d.ts +19 -0
  183. package/dist/core/shared/tool-detection.js +89 -0
  184. package/dist/domains/sdd/index.d.ts +6 -0
  185. package/dist/domains/sdd/index.js +6 -0
  186. package/dist/infrastructures/sdd/index.d.ts +7 -0
  187. package/dist/infrastructures/sdd/index.js +6 -0
  188. package/dist/presentations/cli/sdd/index.d.ts +3 -0
  189. package/dist/presentations/cli/sdd/index.js +3 -0
  190. package/dist/shared/sdd/index.d.ts +3 -0
  191. package/dist/shared/sdd/index.js +2 -0
  192. package/package.json +14 -10
  193. package/schemas/sdd/2-plan.schema.json +207 -2
  194. package/schemas/sdd/5-quality.schema.json +324 -25
  195. package/schemas/sdd/agent-runtime-command-plan.schema.json +212 -0
  196. package/schemas/sdd/agent-runtime-opencode-run-evidence.schema.json +270 -0
  197. package/schemas/sdd/codesdd-plugin.schema.json +171 -0
  198. package/schemas/sdd/deepagent-run-request.schema.json +316 -0
  199. package/schemas/sdd/parallel-feat-automation-plan.schema.json +89 -0
  200. package/schemas/sdd/parallel-feat-scheduler-request.schema.json +116 -0
  201. package/schemas/sdd/parallel-feat-scheduler-result.schema.json +404 -0
  202. package/schemas/sdd/plugin-artifact-manifest.schema.json +109 -0
  203. package/schemas/sdd/plugin-artifact-map.schema.json +223 -0
  204. package/schemas/sdd/plugin-evidence-manifest.schema.json +109 -0
  205. package/schemas/sdd/plugin-language-runtime.schema.json +103 -0
  206. package/schemas/sdd/plugin-package-governance.schema.json +74 -0
  207. package/schemas/sdd/plugin-registry.schema.json +171 -0
  208. package/schemas/sdd/plugin-runtime-invocation-plan.schema.json +109 -0
  209. package/schemas/sdd/quality-evidence-bundle.schema.json +109 -0
  210. package/schemas/sdd/reversa-evidence-bundle.schema.json +466 -0
  211. package/schemas/sdd/sdk-agent-plugin-quality-gate-input.schema.json +168 -0
  212. package/schemas/sdd/sdk-agent-plugin-quality-gate-report.schema.json +160 -0
  213. package/schemas/sdd/workspace-catalog.schema.json +5298 -1409
@@ -0,0 +1,767 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { promises as fs } from 'node:fs';
3
+ import { execFile } from 'node:child_process';
4
+ import path from 'node:path';
5
+ import { promisify } from 'node:util';
6
+ import { parse as parseYaml } from 'yaml';
7
+ import { SddCheckCommand } from './check.js';
8
+ import { loadProjectSddConfig, loadStateSnapshot, resolveSddPaths, } from './state.js';
9
+ import { evaluatePackageSecurityGates } from './package-security-gates.js';
10
+ const execFileAsync = promisify(execFile);
11
+ const CI_PARITY_COMMANDS = [
12
+ 'pnpm run build',
13
+ 'pnpm run sdd:schema:check',
14
+ 'pnpm exec tsc --noEmit',
15
+ 'pnpm run lint',
16
+ 'pnpm run sdd:validate:no-build',
17
+ 'pnpm exec vitest run --testTimeout 30000',
18
+ 'pnpm run check:pack-version',
19
+ ];
20
+ const REQUIRED_SCRIPTS = [
21
+ 'build',
22
+ 'sdd:schema:check',
23
+ 'sdd:validate:no-build',
24
+ 'lint',
25
+ 'test',
26
+ 'check:pack-version',
27
+ 'quality:review',
28
+ ];
29
+ const REQUIRED_SCHEMA_FILES = [
30
+ 'workspace-catalog.schema.json',
31
+ '5-quality.schema.json',
32
+ 'agent-runtime-command-plan.schema.json',
33
+ 'agent-runtime-opencode-run-evidence.schema.json',
34
+ ];
35
+ const COVERAGE_TARGET_PERCENT = 95;
36
+ const CHECK_OWNERS = {
37
+ 'sdd-health': 'codesdd/governance',
38
+ 'active-features': 'codesdd/workspace',
39
+ 'package-metadata': 'release/maintainers',
40
+ 'release-scripts': 'ci/release',
41
+ 'git-working-tree': 'operator/worktree',
42
+ 'coverage-evidence': 'quality/owner',
43
+ 'npmrc-secret-boundary': 'security/owner',
44
+ 'package-security-gates': 'security/owner',
45
+ 'provenance-sbom': 'release/security',
46
+ 'tarball-smoke-rollback': 'release/operations',
47
+ 'schema-artifacts': 'codesdd/schemas',
48
+ 'release-docs': 'docs/release',
49
+ };
50
+ const CHECK_NEXT_ACTIONS = {
51
+ 'sdd-health': 'Run `codesdd sdd check --render --strict` and fix listed blockers.',
52
+ 'active-features': 'Finalize active FEAT workspaces with `codesdd sdd finalize --ref <FEAT-ID>`.',
53
+ 'package-metadata': 'Align `package.json` name/license/bin to canonical release values.',
54
+ 'release-scripts': 'Add missing scripts to `package.json` and rerun CI parity commands.',
55
+ 'git-working-tree': 'Commit or clean non-release changes before running strict readiness.',
56
+ 'coverage-evidence': 'Generate touched-scope coverage (`coverage/coverage-final.json`) at or above 95%.',
57
+ 'npmrc-secret-boundary': 'Remove project-local `.npmrc` and keep it ignored by `.gitignore`.',
58
+ 'package-security-gates': 'Fix package allowlist or secret-scan findings and rerun readiness.',
59
+ 'provenance-sbom': 'Restore provenance/SBOM workflow, script and documentation evidence.',
60
+ 'tarball-smoke-rollback': 'Restore CI tarball smoke and rollback documentation evidence.',
61
+ 'schema-artifacts': 'Regenerate/export required files under `schemas/sdd`.',
62
+ 'release-docs': 'Add missing `docs/release.md`, `docs/security.md` or `README.md`.',
63
+ };
64
+ const CHECK_DELTAS = {
65
+ 'sdd-health': 'Canonical SDD check integrity for strict release gate.',
66
+ 'active-features': 'No unfinished feature workspace can leak into release.',
67
+ 'package-metadata': 'Canonical package identity and executable entrypoint.',
68
+ 'release-scripts': 'Release scripts required for CI parity order are present.',
69
+ 'git-working-tree': 'Release runs from a reproducible, inspectable repository state.',
70
+ 'coverage-evidence': 'Touched source scope has measurable coverage at release threshold.',
71
+ 'npmrc-secret-boundary': 'Project tree cannot carry local npm credentials.',
72
+ 'package-security-gates': 'Package and secret scans must remain blocking.',
73
+ 'provenance-sbom': 'Trusted publishing provenance and SBOM are verifiable.',
74
+ 'tarball-smoke-rollback': 'Tarball smoke and rollback steps are executable and documented.',
75
+ 'schema-artifacts': 'Required schema artifacts are present for consumers.',
76
+ 'release-docs': 'Operator release and security docs are available.',
77
+ };
78
+ const SECTION_LABELS = {
79
+ governance: 'Governance',
80
+ tests: 'Tests',
81
+ release_readiness: 'Release Readiness',
82
+ git_cleanliness: 'Git Cleanliness',
83
+ runtime_env: 'Runtime Env',
84
+ };
85
+ const SECTION_ORDER = [
86
+ 'governance',
87
+ 'tests',
88
+ 'release_readiness',
89
+ 'git_cleanliness',
90
+ 'runtime_env',
91
+ ];
92
+ const CHECK_SECTION_MAP = {
93
+ 'sdd-health': 'governance',
94
+ 'active-features': 'governance',
95
+ 'schema-artifacts': 'governance',
96
+ 'release-docs': 'governance',
97
+ 'coverage-evidence': 'tests',
98
+ 'release-scripts': 'release_readiness',
99
+ 'package-security-gates': 'release_readiness',
100
+ 'provenance-sbom': 'release_readiness',
101
+ 'tarball-smoke-rollback': 'release_readiness',
102
+ 'git-working-tree': 'git_cleanliness',
103
+ 'npmrc-secret-boundary': 'git_cleanliness',
104
+ 'package-metadata': 'runtime_env',
105
+ };
106
+ export async function evaluateReleaseReadiness(projectRoot) {
107
+ const checks = [];
108
+ const packageJson = await readPackageJson(projectRoot);
109
+ const config = await loadProjectSddConfig(projectRoot);
110
+ const paths = resolveSddPaths(projectRoot, config);
111
+ const snapshot = await loadStateSnapshot(paths, config);
112
+ const sddCheck = await new SddCheckCommand().execute(projectRoot, { render: false, strict: true });
113
+ checks.push({
114
+ id: 'sdd-health',
115
+ status: sddCheck.valid ? 'pass' : 'fail',
116
+ summary: sddCheck.valid ? 'CodeSDD check is valid.' : 'CodeSDD check has blocking errors.',
117
+ evidence: sddCheck.valid ? undefined : sddCheck.errors.join(' | '),
118
+ });
119
+ const activeFeatures = snapshot.backlog.items.filter((item) => item.status === 'IN_PROGRESS').map((item) => item.id);
120
+ checks.push({
121
+ id: 'active-features',
122
+ status: activeFeatures.length === 0 ? 'pass' : 'fail',
123
+ summary: activeFeatures.length === 0 ? 'No active FEAT workspaces remain.' : 'Active FEAT workspaces remain.',
124
+ evidence: activeFeatures.join(', ') || undefined,
125
+ });
126
+ checks.push({
127
+ id: 'package-metadata',
128
+ status: packageJson.name === '@devtrack-solution/codesdd' &&
129
+ packageJson.license === 'MIT' &&
130
+ packageJson.bin?.codesdd === 'bin/codesdd.js'
131
+ ? 'pass'
132
+ : 'fail',
133
+ summary: 'Package name, license, and bin entry match release expectations.',
134
+ evidence: `${packageJson.name} ${packageJson.version}`,
135
+ });
136
+ const missingScripts = REQUIRED_SCRIPTS.filter((script) => !packageJson.scripts?.[script]);
137
+ checks.push({
138
+ id: 'release-scripts',
139
+ status: missingScripts.length === 0 ? 'pass' : 'fail',
140
+ summary: missingScripts.length === 0 ? 'Required release validation scripts are present.' : 'Required release validation scripts are missing.',
141
+ evidence: missingScripts.join(', ') || REQUIRED_SCRIPTS.join(', '),
142
+ });
143
+ checks.push(await evaluateGitWorkingTree(projectRoot));
144
+ checks.push(await evaluateCoverageEvidence(projectRoot));
145
+ checks.push(await evaluateNpmConfig(projectRoot));
146
+ checks.push(await evaluatePackageSecurity(projectRoot));
147
+ checks.push(await evaluateProvenanceAndSbom(projectRoot, packageJson));
148
+ checks.push(await evaluateTarballSmokeAndRollback(projectRoot, packageJson));
149
+ checks.push(await evaluateSchemaArtifacts(projectRoot));
150
+ checks.push(await evaluateReleaseDocs(projectRoot));
151
+ const exceptionLedger = await loadReleaseExceptionLedger(projectRoot, checks.map((check) => check.id));
152
+ for (const check of checks) {
153
+ const exceptionState = exceptionLedger.get(check.id) ?? { status: 'none' };
154
+ const classification = classifyCheck(check.status, exceptionState);
155
+ check.classification = classification;
156
+ check.owner = CHECK_OWNERS[check.id] ?? 'codesdd/owner';
157
+ check.delta = CHECK_DELTAS[check.id] ?? 'Release gate contract delta not documented.';
158
+ check.next_action = CHECK_NEXT_ACTIONS[check.id] ?? 'Review gate output and remediate before release.';
159
+ check.exception = exceptionState;
160
+ check.history = buildCheckHistory(check, exceptionState);
161
+ }
162
+ const blockers = checks
163
+ .filter((check) => check.classification === 'failed')
164
+ .map((check) => `${check.id}: ${check.summary}${check.evidence ? ` (${check.evidence})` : ''} [owner=${check.owner}; next=${check.next_action}]`);
165
+ const warnings = checks
166
+ .filter((check) => check.classification === 'follow_up' || check.classification === 'excepted')
167
+ .map((check) => `${check.id}: ${check.summary}${check.evidence ? ` (${check.evidence})` : ''} [classification=${check.classification}; next=${check.next_action}]`);
168
+ const handoffSections = buildHandoffSections(checks);
169
+ return {
170
+ schema_version: 1,
171
+ status: blockers.length > 0 ? 'blocked' : warnings.length > 0 ? 'warning' : 'ready',
172
+ generated_at: new Date().toISOString(),
173
+ blocker_classification: checks
174
+ .filter((check) => check.classification === 'failed' || check.classification === 'excepted')
175
+ .map((check) => ({
176
+ check_id: check.id,
177
+ classification: check.classification ?? 'failed',
178
+ owner: check.owner ?? 'codesdd/owner',
179
+ next_action: check.next_action ?? 'Review gate output and remediate before release.',
180
+ stale_exception: check.exception?.status === 'stale',
181
+ })),
182
+ blockers,
183
+ warnings,
184
+ checks,
185
+ handoff_sections: handoffSections,
186
+ ci_parity_commands: CI_PARITY_COMMANDS,
187
+ };
188
+ }
189
+ async function evaluateGitWorkingTree(projectRoot) {
190
+ if (!existsSync(path.join(projectRoot, '.git'))) {
191
+ return {
192
+ id: 'git-working-tree',
193
+ status: 'warn',
194
+ summary: 'Git working tree could not be verified because .git is absent.',
195
+ evidence: '.git',
196
+ };
197
+ }
198
+ try {
199
+ const { stdout } = await execFileAsync('git', ['status', '--porcelain=v1', '--untracked-files=all'], {
200
+ cwd: projectRoot,
201
+ maxBuffer: 1024 * 1024,
202
+ });
203
+ const entries = stdout.split(/\r?\n/u).filter(Boolean);
204
+ return {
205
+ id: 'git-working-tree',
206
+ status: entries.length === 0 ? 'pass' : 'fail',
207
+ summary: entries.length === 0 ? 'Working tree is clean.' : 'Working tree has uncommitted or untracked files.',
208
+ evidence: entries.slice(0, 12).join(' | ') || undefined,
209
+ };
210
+ }
211
+ catch (error) {
212
+ return {
213
+ id: 'git-working-tree',
214
+ status: 'warn',
215
+ summary: 'Git working tree could not be verified.',
216
+ evidence: error instanceof Error ? error.message : String(error),
217
+ };
218
+ }
219
+ }
220
+ async function evaluateCoverageEvidence(projectRoot) {
221
+ const coveragePath = path.join(projectRoot, 'coverage', 'coverage-final.json');
222
+ if (!existsSync(coveragePath)) {
223
+ return {
224
+ id: 'coverage-evidence',
225
+ status: 'fail',
226
+ summary: 'Coverage evidence is missing.',
227
+ evidence: 'coverage/coverage-final.json',
228
+ };
229
+ }
230
+ try {
231
+ const coverage = JSON.parse(await fs.readFile(coveragePath, 'utf-8'));
232
+ const changedSourceScope = await listChangedSourceScope(projectRoot);
233
+ const globalCoverage = summarizeCoverage(coverage, projectRoot);
234
+ const branchDisposition = globalCoverage.branches.percent < COVERAGE_TARGET_PERCENT
235
+ ? `; global branches ${globalCoverage.branches.percent}% tracked as ratchet`
236
+ : '';
237
+ if (changedSourceScope.size === 0) {
238
+ return {
239
+ id: 'coverage-evidence',
240
+ status: 'pass',
241
+ summary: 'Coverage evidence is present; no touched source scope detected for threshold gating.',
242
+ evidence: `no-touched-source; global statements ${globalCoverage.statements.percent}%, lines ${globalCoverage.lines.percent}%, functions ${globalCoverage.functions.percent}%${branchDisposition}`,
243
+ };
244
+ }
245
+ const touchedCoverage = summarizeCoverage(coverage, projectRoot, changedSourceScope);
246
+ const failingMetrics = ['statements', 'lines', 'functions'].filter((metric) => touchedCoverage[metric].percent < COVERAGE_TARGET_PERCENT);
247
+ const scopeFiles = [...changedSourceScope.keys()].sort();
248
+ const scope = `touched:${scopeFiles.join(',')}`;
249
+ return {
250
+ id: 'coverage-evidence',
251
+ status: failingMetrics.length === 0 ? 'pass' : 'fail',
252
+ summary: failingMetrics.length === 0
253
+ ? 'Coverage evidence meets release target for touched source scope.'
254
+ : 'Coverage evidence is below release target for touched source scope.',
255
+ evidence: `${scope}; statements ${touchedCoverage.statements.percent}%, lines ${touchedCoverage.lines.percent}%, functions ${touchedCoverage.functions.percent}%${branchDisposition}`,
256
+ };
257
+ }
258
+ catch (error) {
259
+ return {
260
+ id: 'coverage-evidence',
261
+ status: 'fail',
262
+ summary: 'Coverage evidence could not be parsed.',
263
+ evidence: error instanceof Error ? error.message : String(error),
264
+ };
265
+ }
266
+ }
267
+ export function formatReleaseReadinessReport(report) {
268
+ const lines = [`Release readiness: ${report.status}`, `Generated at: ${report.generated_at}`];
269
+ if (report.handoff_sections.length > 0) {
270
+ lines.push('Executable handoff sections:');
271
+ for (const section of report.handoff_sections) {
272
+ lines.push(`- [${section.id}] ${section.title}: ${section.status} (owner=${section.owner}; next=${section.next_action})`);
273
+ for (const snapshotLine of section.evidence.human_snapshot) {
274
+ lines.push(` ${snapshotLine}`);
275
+ }
276
+ }
277
+ }
278
+ if (report.blocker_classification.length > 0) {
279
+ lines.push('Blocker classification:');
280
+ for (const blocker of report.blocker_classification) {
281
+ lines.push(`- ${blocker.check_id}: ${blocker.classification} (owner=${blocker.owner}; stale_exception=${blocker.stale_exception ? 'yes' : 'no'}; next=${blocker.next_action})`);
282
+ }
283
+ }
284
+ if (report.blockers.length > 0) {
285
+ lines.push('Blockers:');
286
+ for (const blocker of report.blockers)
287
+ lines.push(`- ${blocker}`);
288
+ }
289
+ if (report.warnings.length > 0) {
290
+ lines.push('Warnings:');
291
+ for (const warning of report.warnings)
292
+ lines.push(`- ${warning}`);
293
+ }
294
+ lines.push('Checks:');
295
+ for (const check of report.checks) {
296
+ lines.push(`- ${check.id}: ${check.status}/${check.classification ?? 'passed'} - ${check.summary}${check.evidence ? ` (${check.evidence})` : ''}${check.next_action ? ` [next=${check.next_action}]` : ''}`);
297
+ }
298
+ lines.push('CI parity commands:');
299
+ for (const command of report.ci_parity_commands)
300
+ lines.push(`- ${command}`);
301
+ return lines.join('\n');
302
+ }
303
+ function classifyCheck(status, exceptionState) {
304
+ if (status === 'pass')
305
+ return 'passed';
306
+ if (exceptionState.status === 'active')
307
+ return 'excepted';
308
+ if (status === 'warn')
309
+ return 'follow_up';
310
+ return 'failed';
311
+ }
312
+ function buildHandoffSections(checks) {
313
+ return SECTION_ORDER.map((sectionId) => {
314
+ const sectionChecks = checks.filter((check) => {
315
+ const mapped = CHECK_SECTION_MAP[check.id];
316
+ return mapped ? mapped === sectionId : sectionId === 'release_readiness';
317
+ });
318
+ const status = summarizeSectionStatus(sectionChecks);
319
+ const actionableCheck = sectionChecks.find((check) => check.classification === 'failed')
320
+ ?? sectionChecks.find((check) => check.classification === 'excepted' || check.classification === 'follow_up')
321
+ ?? sectionChecks[0];
322
+ const owner = actionableCheck?.owner ?? 'codesdd/owner';
323
+ const nextAction = actionableCheck?.next_action ?? 'No pending action.';
324
+ const jsonSnapshotChecks = sectionChecks.map((check) => ({
325
+ id: check.id,
326
+ status: check.status,
327
+ classification: check.classification ?? 'passed',
328
+ owner: check.owner ?? 'codesdd/owner',
329
+ next_action: check.next_action ?? 'Review gate output and remediate before release.',
330
+ evidence: check.evidence,
331
+ }));
332
+ const humanSnapshot = sectionChecks.map((check) => `- ${check.id}: ${check.status}/${check.classification ?? 'passed'} (owner=${check.owner ?? 'codesdd/owner'}; next=${check.next_action ?? 'Review gate output and remediate before release.'})`);
333
+ return {
334
+ id: sectionId,
335
+ title: SECTION_LABELS[sectionId],
336
+ status,
337
+ owner,
338
+ next_action: nextAction,
339
+ checks: sectionChecks,
340
+ evidence: {
341
+ json_snapshot: {
342
+ section_id: sectionId,
343
+ status,
344
+ checks: jsonSnapshotChecks,
345
+ },
346
+ human_snapshot: humanSnapshot,
347
+ },
348
+ };
349
+ });
350
+ }
351
+ function summarizeSectionStatus(checks) {
352
+ if (checks.some((check) => check.classification === 'failed'))
353
+ return 'blocked';
354
+ if (checks.some((check) => check.classification === 'excepted' || check.classification === 'follow_up')) {
355
+ return 'warning';
356
+ }
357
+ return 'ready';
358
+ }
359
+ function buildCheckHistory(check, exceptionState) {
360
+ const history = [`check:${check.status}`];
361
+ if (check.classification) {
362
+ history.push(`classification:${check.classification}`);
363
+ }
364
+ if (exceptionState.status === 'active') {
365
+ history.push('exception:active');
366
+ if (exceptionState.review_deadline) {
367
+ history.push(`exception-review-deadline:${exceptionState.review_deadline}`);
368
+ }
369
+ }
370
+ else if (exceptionState.status === 'stale') {
371
+ history.push('exception:stale-reblocked');
372
+ if (exceptionState.review_deadline) {
373
+ history.push(`exception-expired:${exceptionState.review_deadline}`);
374
+ }
375
+ }
376
+ return history;
377
+ }
378
+ async function loadReleaseExceptionLedger(projectRoot, knownCheckIds) {
379
+ const ledger = new Map();
380
+ for (const area of ['active', 'planned']) {
381
+ const areaRoot = path.join(projectRoot, '.sdd', area);
382
+ if (!existsSync(areaRoot))
383
+ continue;
384
+ const features = await fs.readdir(areaRoot, { withFileTypes: true }).catch(() => []);
385
+ for (const feature of features) {
386
+ if (!feature.isDirectory())
387
+ continue;
388
+ const qualityPath = path.join(areaRoot, feature.name, '5-quality.yaml');
389
+ if (!existsSync(qualityPath))
390
+ continue;
391
+ const entries = await readQualityExceptionEntries(qualityPath, knownCheckIds);
392
+ for (const [checkId, entry] of entries) {
393
+ const previous = ledger.get(checkId);
394
+ if (!previous || (previous.status !== 'stale' && entry.status === 'stale')) {
395
+ ledger.set(checkId, {
396
+ status: entry.status,
397
+ source: entry.source,
398
+ scope: entry.scope,
399
+ reason: entry.reason,
400
+ accepted_risk: entry.accepted_risk,
401
+ compensating_control: entry.compensating_control,
402
+ review_deadline: entry.review_deadline,
403
+ approver: entry.approver,
404
+ });
405
+ }
406
+ }
407
+ }
408
+ }
409
+ return ledger;
410
+ }
411
+ async function readQualityExceptionEntries(qualityPath, knownCheckIds) {
412
+ const byCheck = new Map();
413
+ const raw = await fs.readFile(qualityPath, 'utf-8').catch(() => '');
414
+ if (!raw.trim())
415
+ return byCheck;
416
+ const parsed = parseYaml(raw);
417
+ const exceptions = Array.isArray(parsed?.exceptions) ? parsed.exceptions : [];
418
+ for (const exception of exceptions) {
419
+ if (!exception || typeof exception !== 'object')
420
+ continue;
421
+ const entry = exception;
422
+ const checkIds = resolveExceptionCheckIds(entry, knownCheckIds);
423
+ if (checkIds.length === 0)
424
+ continue;
425
+ const reviewDeadline = readString(entry.review_deadline)
426
+ ?? readString(entry.deadline)
427
+ ?? readString(entry.expires_at)
428
+ ?? readString(entry.expires_on);
429
+ const stale = isDeadlineStale(reviewDeadline);
430
+ const normalized = {
431
+ status: stale ? 'stale' : 'active',
432
+ source: qualityPath,
433
+ scope: readString(entry.scope),
434
+ reason: readString(entry.reason),
435
+ accepted_risk: readString(entry.accepted_risk),
436
+ compensating_control: readString(entry.compensating_control),
437
+ review_deadline: reviewDeadline,
438
+ approver: readString(entry.approver),
439
+ };
440
+ for (const checkId of checkIds) {
441
+ byCheck.set(checkId, normalized);
442
+ }
443
+ }
444
+ return byCheck;
445
+ }
446
+ function resolveExceptionCheckIds(entry, knownCheckIds) {
447
+ const explicit = [
448
+ ...readStringArray(entry.check_ids),
449
+ ...readStringArray(entry.check_id),
450
+ ...readStringArray(entry.release_check_ids),
451
+ ...readStringArray(entry.release_check_id),
452
+ ];
453
+ if (explicit.length > 0) {
454
+ return explicit.filter((value) => knownCheckIds.includes(value));
455
+ }
456
+ const scope = readString(entry.scope);
457
+ if (!scope)
458
+ return [];
459
+ const normalizedScope = scope.toLowerCase();
460
+ return knownCheckIds.filter((checkId) => normalizedScope.includes(checkId.toLowerCase()));
461
+ }
462
+ function readString(value) {
463
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
464
+ }
465
+ function readStringArray(value) {
466
+ if (Array.isArray(value)) {
467
+ return value.flatMap((entry) => (typeof entry === 'string' && entry.trim().length > 0 ? [entry.trim()] : []));
468
+ }
469
+ if (typeof value === 'string' && value.trim().length > 0) {
470
+ return [value.trim()];
471
+ }
472
+ return [];
473
+ }
474
+ function isDeadlineStale(reviewDeadline) {
475
+ if (!reviewDeadline)
476
+ return false;
477
+ const parsed = Date.parse(reviewDeadline);
478
+ if (Number.isNaN(parsed))
479
+ return false;
480
+ return parsed < Date.now();
481
+ }
482
+ async function readPackageJson(projectRoot) {
483
+ const content = await fs.readFile(path.join(projectRoot, 'package.json'), 'utf-8');
484
+ return JSON.parse(content);
485
+ }
486
+ async function listChangedSourceScope(projectRoot) {
487
+ const changed = new Map();
488
+ if (!existsSync(path.join(projectRoot, '.git')))
489
+ return changed;
490
+ try {
491
+ const { stdout: status } = await execFileAsync('git', ['status', '--porcelain=v1', '--untracked-files=all'], {
492
+ cwd: projectRoot,
493
+ maxBuffer: 1024 * 1024,
494
+ });
495
+ for (const line of status.split(/\r?\n/u).filter(Boolean)) {
496
+ const statusCode = line.slice(0, 2);
497
+ const relativePath = line
498
+ .slice(3)
499
+ .trim()
500
+ .split(' -> ')
501
+ .at(-1) || '';
502
+ if (!/^src\/.*\.tsx?$/u.test(relativePath))
503
+ continue;
504
+ changed.set(path.normalize(relativePath), statusCode === '??' ? null : new Set());
505
+ }
506
+ await addDiffSourceLines(projectRoot, changed, ['diff', '--unified=0', '--', 'src']);
507
+ if (changed.size === 0) {
508
+ const upstreamBase = await resolveUpstreamDiffBase(projectRoot);
509
+ if (upstreamBase) {
510
+ await addDiffSourceLines(projectRoot, changed, ['diff', '--unified=0', `${upstreamBase}...HEAD`, '--', 'src']);
511
+ }
512
+ }
513
+ }
514
+ catch {
515
+ return changed;
516
+ }
517
+ return changed;
518
+ }
519
+ async function resolveUpstreamDiffBase(projectRoot) {
520
+ try {
521
+ const { stdout: upstreamStdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'], {
522
+ cwd: projectRoot,
523
+ maxBuffer: 1024 * 1024,
524
+ });
525
+ const upstream = upstreamStdout.trim();
526
+ if (!upstream)
527
+ return undefined;
528
+ const [{ stdout: baseStdout }, { stdout: headStdout }] = await Promise.all([
529
+ execFileAsync('git', ['merge-base', 'HEAD', upstream], {
530
+ cwd: projectRoot,
531
+ maxBuffer: 1024 * 1024,
532
+ }),
533
+ execFileAsync('git', ['rev-parse', 'HEAD'], {
534
+ cwd: projectRoot,
535
+ maxBuffer: 1024 * 1024,
536
+ }),
537
+ ]);
538
+ const base = baseStdout.trim();
539
+ const head = headStdout.trim();
540
+ return base && base !== head ? base : undefined;
541
+ }
542
+ catch {
543
+ return undefined;
544
+ }
545
+ }
546
+ async function addDiffSourceLines(projectRoot, changed, diffArgs) {
547
+ const { stdout: diff } = await execFileAsync('git', diffArgs, {
548
+ cwd: projectRoot,
549
+ maxBuffer: 1024 * 1024,
550
+ });
551
+ let currentFile = '';
552
+ let newLine = 0;
553
+ for (const line of diff.split(/\r?\n/u)) {
554
+ if (line.startsWith('+++ b/')) {
555
+ currentFile = path.normalize(line.slice('+++ b/'.length));
556
+ if (/^src[/\\].*\.tsx?$/u.test(currentFile) && !changed.has(currentFile)) {
557
+ changed.set(currentFile, new Set());
558
+ }
559
+ continue;
560
+ }
561
+ const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/u);
562
+ if (hunk) {
563
+ newLine = Number(hunk[1]);
564
+ continue;
565
+ }
566
+ if (!currentFile || !changed.has(currentFile) || changed.get(currentFile) === null)
567
+ continue;
568
+ if (line.startsWith('+') && !line.startsWith('+++')) {
569
+ changed.get(currentFile)?.add(newLine);
570
+ newLine += 1;
571
+ }
572
+ else if (!line.startsWith('-')) {
573
+ newLine += 1;
574
+ }
575
+ }
576
+ }
577
+ function summarizeCoverage(coverage, projectRoot, changedSourceScope = new Map()) {
578
+ const totals = {
579
+ statements: { covered: 0, total: 0, percent: 100 },
580
+ branches: { covered: 0, total: 0, percent: 100 },
581
+ functions: { covered: 0, total: 0, percent: 100 },
582
+ lines: { covered: 0, total: 0, percent: 100 },
583
+ };
584
+ for (const [absolutePath, fileCoverage] of Object.entries(coverage)) {
585
+ const relativePath = path.normalize(path.relative(projectRoot, fileCoverage.path || absolutePath));
586
+ const isSource = relativePath.startsWith(`src${path.sep}`) && /\.tsx?$/u.test(relativePath);
587
+ if (!isSource)
588
+ continue;
589
+ const changedLines = changedSourceScope.get(relativePath);
590
+ if (changedSourceScope.size > 0 && changedLines === undefined)
591
+ continue;
592
+ const lineHits = new Map();
593
+ for (const [statementId, count] of Object.entries(fileCoverage.s || {})) {
594
+ const line = fileCoverage.statementMap?.[statementId]?.start?.line;
595
+ if (changedLines && (line === undefined || !changedLines.has(line)))
596
+ continue;
597
+ totals.statements.total += 1;
598
+ if (count > 0)
599
+ totals.statements.covered += 1;
600
+ if (line !== undefined) {
601
+ lineHits.set(line, (lineHits.get(line) || 0) + (count > 0 ? 1 : 0));
602
+ }
603
+ }
604
+ for (const [functionId, count] of Object.entries(fileCoverage.f || {})) {
605
+ const line = fileCoverage.fnMap?.[functionId]?.decl?.start?.line || fileCoverage.fnMap?.[functionId]?.loc?.start?.line;
606
+ if (changedLines && (line === undefined || !changedLines.has(line)))
607
+ continue;
608
+ totals.functions.total += 1;
609
+ if (count > 0)
610
+ totals.functions.covered += 1;
611
+ }
612
+ for (const counts of Object.values(fileCoverage.b || {})) {
613
+ for (const count of counts) {
614
+ totals.branches.total += 1;
615
+ if (count > 0)
616
+ totals.branches.covered += 1;
617
+ }
618
+ }
619
+ for (const count of lineHits.values()) {
620
+ totals.lines.total += 1;
621
+ if (count > 0)
622
+ totals.lines.covered += 1;
623
+ }
624
+ }
625
+ for (const metric of Object.values(totals)) {
626
+ metric.percent = roundPercent(metric.covered, metric.total);
627
+ }
628
+ return totals;
629
+ }
630
+ function roundPercent(covered, total) {
631
+ return total === 0 ? 100 : Number(((covered / total) * 100).toFixed(2));
632
+ }
633
+ async function evaluateNpmConfig(projectRoot) {
634
+ const npmrcPath = path.join(projectRoot, '.npmrc');
635
+ const gitignorePath = path.join(projectRoot, '.gitignore');
636
+ const gitignore = await fs.readFile(gitignorePath, 'utf-8').catch(() => '');
637
+ const npmrcIgnored = gitignore.split(/\r?\n/u).some((line) => line.trim() === '.npmrc');
638
+ if (existsSync(npmrcPath)) {
639
+ return {
640
+ id: 'npmrc-secret-boundary',
641
+ status: 'fail',
642
+ summary: 'Project-local .npmrc exists and must not be present for release readiness.',
643
+ evidence: '.npmrc',
644
+ };
645
+ }
646
+ return {
647
+ id: 'npmrc-secret-boundary',
648
+ status: npmrcIgnored ? 'pass' : 'warn',
649
+ summary: npmrcIgnored ? '.npmrc is absent and ignored.' : '.npmrc is absent but not explicitly ignored.',
650
+ evidence: npmrcIgnored ? undefined : '.gitignore',
651
+ };
652
+ }
653
+ async function evaluatePackageSecurity(projectRoot) {
654
+ const report = await evaluatePackageSecurityGates(projectRoot);
655
+ const issues = [
656
+ ...report.package_allowlist.issues,
657
+ ...report.secret_scan.issues,
658
+ ];
659
+ return {
660
+ id: 'package-security-gates',
661
+ status: report.status === 'pass' ? 'pass' : 'fail',
662
+ summary: report.status === 'pass'
663
+ ? 'Package allowlist and high-confidence secret scan passed.'
664
+ : 'Package allowlist or high-confidence secret scan failed.',
665
+ evidence: issues.length > 0
666
+ ? issues.map((issue) => `${issue.code}:${issue.path}`).join(', ')
667
+ : `${report.package_allowlist.allowed_files.length} package entries; ${report.secret_scan.scanned_files} files scanned`,
668
+ };
669
+ }
670
+ async function evaluateSchemaArtifacts(projectRoot) {
671
+ const missing = REQUIRED_SCHEMA_FILES.filter((fileName) => !existsSync(path.join(projectRoot, 'schemas', 'sdd', fileName)));
672
+ return {
673
+ id: 'schema-artifacts',
674
+ status: missing.length === 0 ? 'pass' : 'fail',
675
+ summary: missing.length === 0 ? 'Required SDD schema artifacts are present.' : 'Required SDD schema artifacts are missing.',
676
+ evidence: missing.join(', ') || REQUIRED_SCHEMA_FILES.join(', '),
677
+ };
678
+ }
679
+ async function evaluateProvenanceAndSbom(projectRoot, packageJson) {
680
+ const workflowPath = path.join(projectRoot, '.github', 'workflows', 'release-prepare.yml');
681
+ const releaseDocPath = path.join(projectRoot, 'docs', 'release.md');
682
+ const securityDocPath = path.join(projectRoot, 'docs', 'security.md');
683
+ const workflow = await fs.readFile(workflowPath, 'utf-8').catch(() => '');
684
+ const releaseDoc = await fs.readFile(releaseDocPath, 'utf-8').catch(() => '');
685
+ const securityDoc = await fs.readFile(securityDocPath, 'utf-8').catch(() => '');
686
+ const docText = `${releaseDoc}\n${securityDoc}`;
687
+ const missing = [];
688
+ if (packageJson.publishConfig?.provenance !== true) {
689
+ missing.push('publishConfig.provenance=true');
690
+ }
691
+ if (!packageJson.scripts?.['release:sbom']) {
692
+ missing.push('scripts.release:sbom');
693
+ }
694
+ if (!/id-token:\s*write/u.test(workflow)) {
695
+ missing.push('release workflow id-token: write');
696
+ }
697
+ if (!/changesets\/action|npm\s+publish/u.test(workflow)) {
698
+ missing.push('release workflow publish step');
699
+ }
700
+ if (!/SBOM|Software Bill of Materials|npm sbom|CycloneDX|SPDX/iu.test(docText)) {
701
+ missing.push('SBOM documentation');
702
+ }
703
+ if (!/trusted publishing|OIDC|provenance/iu.test(docText)) {
704
+ missing.push('trusted publishing/provenance documentation');
705
+ }
706
+ return {
707
+ id: 'provenance-sbom',
708
+ status: missing.length === 0 ? 'pass' : 'fail',
709
+ summary: missing.length === 0
710
+ ? 'Trusted publishing provenance and SBOM evidence are configured.'
711
+ : 'Trusted publishing provenance or SBOM evidence is incomplete.',
712
+ evidence: missing.join(', ') || 'publishConfig.provenance, release:sbom, OIDC workflow, docs/release.md, docs/security.md',
713
+ };
714
+ }
715
+ async function evaluateTarballSmokeAndRollback(projectRoot, packageJson) {
716
+ const workflowPath = path.join(projectRoot, '.github', 'workflows', 'ci.yml');
717
+ const releaseDocPath = path.join(projectRoot, 'docs', 'release.md');
718
+ const workflow = await fs.readFile(workflowPath, 'utf-8').catch(() => '');
719
+ const releaseDoc = await fs.readFile(releaseDocPath, 'utf-8').catch(() => '');
720
+ const missing = [];
721
+ if (!packageJson.scripts?.['check:pack-version']) {
722
+ missing.push('scripts.check:pack-version');
723
+ }
724
+ if (!/pnpm\s+run\s+check:pack-version/u.test(workflow)) {
725
+ missing.push('CI check:pack-version step');
726
+ }
727
+ if (!/(npm|pnpm)\s+pack/u.test(workflow)) {
728
+ missing.push('CI tarball pack step');
729
+ }
730
+ if (!/npm\s+install\s+-g\s+\.\/.*--force/u.test(workflow.replace(/\s+/gu, ' '))) {
731
+ missing.push('CI global tarball install step');
732
+ }
733
+ if (!/codesdd\s+--version/u.test(workflow)) {
734
+ missing.push('CI CLI version smoke');
735
+ }
736
+ if (!/codesdd\s+install\s+--tools\s+none/u.test(workflow)) {
737
+ missing.push('CI bootstrap install smoke');
738
+ }
739
+ if (!/(manual fallback release|tarball)/iu.test(releaseDoc)) {
740
+ missing.push('tarball fallback release docs');
741
+ }
742
+ if (!/npm\s+deprecate\s+@devtrack-solution\/codesdd@<broken-version>/u.test(releaseDoc)) {
743
+ missing.push('npm deprecate rollback command');
744
+ }
745
+ if (!/(rollback strategy|cut a patch release|publish and update release notes)/iu.test(releaseDoc)) {
746
+ missing.push('rollback operator steps');
747
+ }
748
+ return {
749
+ id: 'tarball-smoke-rollback',
750
+ status: missing.length === 0 ? 'pass' : 'fail',
751
+ summary: missing.length === 0
752
+ ? 'Tarball install smoke and rollback evidence are configured.'
753
+ : 'Tarball install smoke or rollback evidence is incomplete.',
754
+ evidence: missing.join(', ') || '.github/workflows/ci.yml, docs/release.md, scripts.check:pack-version',
755
+ };
756
+ }
757
+ async function evaluateReleaseDocs(projectRoot) {
758
+ const required = ['docs/release.md', 'docs/security.md', 'README.md'];
759
+ const missing = required.filter((relativePath) => !existsSync(path.join(projectRoot, relativePath)));
760
+ return {
761
+ id: 'release-docs',
762
+ status: missing.length === 0 ? 'pass' : 'fail',
763
+ summary: missing.length === 0 ? 'Release and security documentation are present.' : 'Release or security documentation is missing.',
764
+ evidence: missing.join(', ') || required.join(', '),
765
+ };
766
+ }
767
+ //# sourceMappingURL=release-readiness.js.map