@devtrack-solution/codesdd 1.2.3 → 1.2.4-rc3

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 (139) hide show
  1. package/.sdd/skills/curated/devtrack-api/SKILL.md +12 -5
  2. package/.sdd/skills/curated/devtrack-api/agents/claude-code.yaml +8 -0
  3. package/.sdd/skills/curated/devtrack-api/agents/codex.yaml +8 -0
  4. package/.sdd/skills/curated/devtrack-api/agents/cursor.yaml +8 -0
  5. package/.sdd/skills/curated/devtrack-api/agents/gemini.yaml +8 -0
  6. package/.sdd/skills/curated/devtrack-api/agents/kimi.yaml +8 -0
  7. package/.sdd/skills/curated/devtrack-api/agents/openai.yaml +4 -2
  8. package/.sdd/skills/curated/devtrack-api/agents/opencode.yaml +10 -0
  9. package/.sdd/skills/curated/devtrack-api/references/application-presentation.md +2 -2
  10. package/.sdd/skills/curated/devtrack-api/references/contract-pack.yaml +55 -0
  11. package/.sdd/skills/curated/devtrack-api/references/domain-modeling.md +13 -13
  12. package/.sdd/skills/curated/devtrack-api/references/foundation-layout.md +2 -3
  13. package/.sdd/skills/curated/devtrack-api/references/implementation-checklist.md +1 -1
  14. package/.sdd/skills/curated/devtrack-api/references/portable-agent-contract.md +41 -0
  15. package/.sdd/skills/curated/devtrack-api/references/typeorm-infrastructure.md +7 -9
  16. package/README.md +159 -5
  17. package/dist/applications/sdd/index.d.ts +16 -0
  18. package/dist/applications/sdd/index.js +16 -0
  19. package/dist/commands/config.js +171 -10
  20. package/dist/commands/sdd/execution.js +345 -15
  21. package/dist/commands/sdd/plugin.js +5 -0
  22. package/dist/commands/sdd/shared.d.ts +1 -0
  23. package/dist/commands/sdd/shared.js +10 -0
  24. package/dist/commands/sdd.js +38 -3
  25. package/dist/core/cli/command-matrix.js +9 -0
  26. package/dist/core/cli-command-quality.js +9 -0
  27. package/dist/core/completions/command-registry.js +45 -0
  28. package/dist/core/config-schema.d.ts +18 -1
  29. package/dist/core/config-schema.js +48 -5
  30. package/dist/core/global-config.d.ts +16 -0
  31. package/dist/core/sdd/agent-binding.d.ts +10 -10
  32. package/dist/core/sdd/agent-runtime-contract.d.ts +204 -0
  33. package/dist/core/sdd/agent-runtime-contract.js +200 -0
  34. package/dist/core/sdd/check.d.ts +2 -0
  35. package/dist/core/sdd/check.js +40 -2
  36. package/dist/core/sdd/coordination/coordination-adapters.d.ts +15 -8
  37. package/dist/core/sdd/coordination/coordination-adapters.js +43 -15
  38. package/dist/core/sdd/coordination/index.d.ts +1 -0
  39. package/dist/core/sdd/coordination/index.js +1 -0
  40. package/dist/core/sdd/coordination/redis-runtime.d.ts +131 -0
  41. package/dist/core/sdd/coordination/redis-runtime.js +698 -0
  42. package/dist/core/sdd/deepagent-contracts.d.ts +98 -4
  43. package/dist/core/sdd/deepagent-contracts.js +62 -0
  44. package/dist/core/sdd/default-bootstrap-files.d.ts +2 -2
  45. package/dist/core/sdd/default-bootstrap-files.js +14 -8
  46. package/dist/core/sdd/default-skills.js +108 -4
  47. package/dist/core/sdd/devtrack-api-appliance.d.ts +8 -1
  48. package/dist/core/sdd/devtrack-api-appliance.js +46 -23
  49. package/dist/core/sdd/docs-sync.js +21 -15
  50. package/dist/core/sdd/domain/capability-diff.d.ts +63 -0
  51. package/dist/core/sdd/domain/capability-diff.js +200 -0
  52. package/dist/core/sdd/domain/change-safety-guardrails.d.ts +74 -0
  53. package/dist/core/sdd/domain/change-safety-guardrails.js +333 -0
  54. package/dist/core/sdd/domain/semantic-intent-classifier.d.ts +29 -0
  55. package/dist/core/sdd/domain/semantic-intent-classifier.js +117 -0
  56. package/dist/core/sdd/foundation-artifact-map-validator.d.ts +16 -0
  57. package/dist/core/sdd/foundation-artifact-map-validator.js +71 -0
  58. package/dist/core/sdd/foundation-layer-manifest.d.ts +24 -0
  59. package/dist/core/sdd/foundation-layer-manifest.js +117 -0
  60. package/dist/core/sdd/intent-guard.d.ts +22 -0
  61. package/dist/core/sdd/intent-guard.js +67 -0
  62. package/dist/core/sdd/json-schema.js +9 -1
  63. package/dist/core/sdd/legacy-operations.js +76 -1
  64. package/dist/core/sdd/migrate-workspace.js +39 -0
  65. package/dist/core/sdd/package-security-gates.d.ts +21 -0
  66. package/dist/core/sdd/package-security-gates.js +119 -0
  67. package/dist/core/sdd/package-structure-gate.js +3 -8
  68. package/dist/core/sdd/parallel-feat-automation.d.ts +181 -3
  69. package/dist/core/sdd/parallel-feat-automation.js +212 -0
  70. package/dist/core/sdd/plugin-broker.d.ts +223 -4
  71. package/dist/core/sdd/plugin-broker.js +10 -0
  72. package/dist/core/sdd/plugin-cli.d.ts +30 -0
  73. package/dist/core/sdd/plugin-cli.js +70 -3
  74. package/dist/core/sdd/plugin-evidence.d.ts +73 -0
  75. package/dist/core/sdd/plugin-manifest.d.ts +69 -1
  76. package/dist/core/sdd/plugin-manifest.js +10 -0
  77. package/dist/core/sdd/plugin-policy-pack.d.ts +1 -1
  78. package/dist/core/sdd/plugin-registry.d.ts +141 -5
  79. package/dist/core/sdd/plugin-sdk-contract.d.ts +363 -0
  80. package/dist/core/sdd/plugin-sdk-contract.js +268 -0
  81. package/dist/core/sdd/plugin-skill-binding.d.ts +1 -1
  82. package/dist/core/sdd/quality-validation.d.ts +84 -11
  83. package/dist/core/sdd/release-readiness.d.ts +19 -0
  84. package/dist/core/sdd/release-readiness.js +472 -0
  85. package/dist/core/sdd/runtime-boundary-contract.d.ts +45 -0
  86. package/dist/core/sdd/runtime-boundary-contract.js +90 -0
  87. package/dist/core/sdd/sdk-agent-plugin-quality-gates.d.ts +150 -0
  88. package/dist/core/sdd/sdk-agent-plugin-quality-gates.js +258 -0
  89. package/dist/core/sdd/services/agent-run.service.d.ts +38 -6
  90. package/dist/core/sdd/services/agent-run.service.js +73 -1
  91. package/dist/core/sdd/services/capability-diff.service.d.ts +18 -0
  92. package/dist/core/sdd/services/capability-diff.service.js +26 -0
  93. package/dist/core/sdd/services/change-safety-preflight.service.d.ts +17 -0
  94. package/dist/core/sdd/services/change-safety-preflight.service.js +17 -0
  95. package/dist/core/sdd/services/context.service.d.ts +43 -340
  96. package/dist/core/sdd/services/context.service.js +323 -9
  97. package/dist/core/sdd/services/finalize.service.d.ts +25 -0
  98. package/dist/core/sdd/services/finalize.service.js +178 -16
  99. package/dist/core/sdd/services/frontend-impact.service.d.ts +1 -1
  100. package/dist/core/sdd/services/semantic-intent-classifier.service.d.ts +6 -0
  101. package/dist/core/sdd/services/semantic-intent-classifier.service.js +7 -0
  102. package/dist/core/sdd/state.d.ts +1 -0
  103. package/dist/core/sdd/state.js +251 -29
  104. package/dist/core/sdd/store/sdd-stores.js +2 -2
  105. package/dist/core/sdd/structural-health.d.ts +13 -13
  106. package/dist/core/sdd/types.d.ts +27 -12
  107. package/dist/core/sdd/types.js +4 -0
  108. package/dist/core/sdd/views.js +17 -0
  109. package/dist/core/sdd/workspace-schemas.d.ts +387 -7
  110. package/dist/core/sdd/workspace-schemas.js +196 -64
  111. package/dist/domains/sdd/index.d.ts +6 -0
  112. package/dist/domains/sdd/index.js +6 -0
  113. package/dist/infrastructures/sdd/index.d.ts +7 -0
  114. package/dist/infrastructures/sdd/index.js +6 -0
  115. package/dist/presentations/cli/sdd/index.d.ts +3 -0
  116. package/dist/presentations/cli/sdd/index.js +3 -0
  117. package/dist/shared/sdd/index.d.ts +3 -0
  118. package/dist/shared/sdd/index.js +2 -0
  119. package/package.json +9 -6
  120. package/schemas/sdd/2-plan.schema.json +207 -2
  121. package/schemas/sdd/5-quality.schema.json +281 -25
  122. package/schemas/sdd/agent-runtime-command-plan.schema.json +212 -0
  123. package/schemas/sdd/agent-runtime-opencode-run-evidence.schema.json +270 -0
  124. package/schemas/sdd/codesdd-plugin.schema.json +171 -0
  125. package/schemas/sdd/deepagent-run-request.schema.json +316 -0
  126. package/schemas/sdd/parallel-feat-automation-plan.schema.json +89 -0
  127. package/schemas/sdd/parallel-feat-scheduler-request.schema.json +116 -0
  128. package/schemas/sdd/parallel-feat-scheduler-result.schema.json +404 -0
  129. package/schemas/sdd/plugin-artifact-manifest.schema.json +109 -0
  130. package/schemas/sdd/plugin-artifact-map.schema.json +223 -0
  131. package/schemas/sdd/plugin-evidence-manifest.schema.json +109 -0
  132. package/schemas/sdd/plugin-language-runtime.schema.json +103 -0
  133. package/schemas/sdd/plugin-package-governance.schema.json +74 -0
  134. package/schemas/sdd/plugin-registry.schema.json +171 -0
  135. package/schemas/sdd/plugin-runtime-invocation-plan.schema.json +109 -0
  136. package/schemas/sdd/quality-evidence-bundle.schema.json +109 -0
  137. package/schemas/sdd/sdk-agent-plugin-quality-gate-input.schema.json +168 -0
  138. package/schemas/sdd/sdk-agent-plugin-quality-gate-report.schema.json +160 -0
  139. package/schemas/sdd/workspace-catalog.schema.json +3776 -398
@@ -0,0 +1,472 @@
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 { SddCheckCommand } from './check.js';
7
+ import { loadProjectSddConfig, loadStateSnapshot, resolveSddPaths, } from './state.js';
8
+ import { evaluatePackageSecurityGates } from './package-security-gates.js';
9
+ const execFileAsync = promisify(execFile);
10
+ const CI_PARITY_COMMANDS = [
11
+ 'pnpm run build',
12
+ 'pnpm run sdd:schema:check',
13
+ 'pnpm exec tsc --noEmit',
14
+ 'pnpm run lint',
15
+ 'pnpm run sdd:validate:no-build',
16
+ 'pnpm exec vitest run --testTimeout 30000',
17
+ 'pnpm run check:pack-version',
18
+ ];
19
+ const REQUIRED_SCRIPTS = [
20
+ 'build',
21
+ 'sdd:schema:check',
22
+ 'sdd:validate:no-build',
23
+ 'lint',
24
+ 'test',
25
+ 'check:pack-version',
26
+ 'quality:review',
27
+ ];
28
+ const REQUIRED_SCHEMA_FILES = [
29
+ 'workspace-catalog.schema.json',
30
+ '5-quality.schema.json',
31
+ 'agent-runtime-command-plan.schema.json',
32
+ 'agent-runtime-opencode-run-evidence.schema.json',
33
+ ];
34
+ const COVERAGE_TARGET_PERCENT = 95;
35
+ export async function evaluateReleaseReadiness(projectRoot) {
36
+ const checks = [];
37
+ const packageJson = await readPackageJson(projectRoot);
38
+ const config = await loadProjectSddConfig(projectRoot);
39
+ const paths = resolveSddPaths(projectRoot, config);
40
+ const snapshot = await loadStateSnapshot(paths, config);
41
+ const sddCheck = await new SddCheckCommand().execute(projectRoot, { render: false, strict: true });
42
+ checks.push({
43
+ id: 'sdd-health',
44
+ status: sddCheck.valid ? 'pass' : 'fail',
45
+ summary: sddCheck.valid ? 'CodeSDD check is valid.' : 'CodeSDD check has blocking errors.',
46
+ evidence: sddCheck.valid ? undefined : sddCheck.errors.join(' | '),
47
+ });
48
+ const activeFeatures = snapshot.backlog.items.filter((item) => item.status === 'IN_PROGRESS').map((item) => item.id);
49
+ checks.push({
50
+ id: 'active-features',
51
+ status: activeFeatures.length === 0 ? 'pass' : 'fail',
52
+ summary: activeFeatures.length === 0 ? 'No active FEAT workspaces remain.' : 'Active FEAT workspaces remain.',
53
+ evidence: activeFeatures.join(', ') || undefined,
54
+ });
55
+ checks.push({
56
+ id: 'package-metadata',
57
+ status: packageJson.name === '@devtrack-solution/codesdd' &&
58
+ packageJson.license === 'MIT' &&
59
+ packageJson.bin?.codesdd === 'bin/codesdd.js'
60
+ ? 'pass'
61
+ : 'fail',
62
+ summary: 'Package name, license, and bin entry match release expectations.',
63
+ evidence: `${packageJson.name} ${packageJson.version}`,
64
+ });
65
+ const missingScripts = REQUIRED_SCRIPTS.filter((script) => !packageJson.scripts?.[script]);
66
+ checks.push({
67
+ id: 'release-scripts',
68
+ status: missingScripts.length === 0 ? 'pass' : 'fail',
69
+ summary: missingScripts.length === 0 ? 'Required release validation scripts are present.' : 'Required release validation scripts are missing.',
70
+ evidence: missingScripts.join(', ') || REQUIRED_SCRIPTS.join(', '),
71
+ });
72
+ checks.push(await evaluateGitWorkingTree(projectRoot));
73
+ checks.push(await evaluateCoverageEvidence(projectRoot));
74
+ checks.push(await evaluateNpmConfig(projectRoot));
75
+ checks.push(await evaluatePackageSecurity(projectRoot));
76
+ checks.push(await evaluateProvenanceAndSbom(projectRoot, packageJson));
77
+ checks.push(await evaluateTarballSmokeAndRollback(projectRoot, packageJson));
78
+ checks.push(await evaluateSchemaArtifacts(projectRoot));
79
+ checks.push(await evaluateReleaseDocs(projectRoot));
80
+ const blockers = checks
81
+ .filter((check) => check.status === 'fail')
82
+ .map((check) => `${check.id}: ${check.summary}${check.evidence ? ` (${check.evidence})` : ''}`);
83
+ const warnings = checks
84
+ .filter((check) => check.status === 'warn')
85
+ .map((check) => `${check.id}: ${check.summary}${check.evidence ? ` (${check.evidence})` : ''}`);
86
+ return {
87
+ schema_version: 1,
88
+ status: blockers.length > 0 ? 'blocked' : warnings.length > 0 ? 'warning' : 'ready',
89
+ generated_at: new Date().toISOString(),
90
+ blockers,
91
+ warnings,
92
+ checks,
93
+ ci_parity_commands: CI_PARITY_COMMANDS,
94
+ };
95
+ }
96
+ async function evaluateGitWorkingTree(projectRoot) {
97
+ if (!existsSync(path.join(projectRoot, '.git'))) {
98
+ return {
99
+ id: 'git-working-tree',
100
+ status: 'warn',
101
+ summary: 'Git working tree could not be verified because .git is absent.',
102
+ evidence: '.git',
103
+ };
104
+ }
105
+ try {
106
+ const { stdout } = await execFileAsync('git', ['status', '--porcelain=v1', '--untracked-files=all'], {
107
+ cwd: projectRoot,
108
+ maxBuffer: 1024 * 1024,
109
+ });
110
+ const entries = stdout.split(/\r?\n/u).filter(Boolean);
111
+ return {
112
+ id: 'git-working-tree',
113
+ status: entries.length === 0 ? 'pass' : 'fail',
114
+ summary: entries.length === 0 ? 'Working tree is clean.' : 'Working tree has uncommitted or untracked files.',
115
+ evidence: entries.slice(0, 12).join(' | ') || undefined,
116
+ };
117
+ }
118
+ catch (error) {
119
+ return {
120
+ id: 'git-working-tree',
121
+ status: 'warn',
122
+ summary: 'Git working tree could not be verified.',
123
+ evidence: error instanceof Error ? error.message : String(error),
124
+ };
125
+ }
126
+ }
127
+ async function evaluateCoverageEvidence(projectRoot) {
128
+ const coveragePath = path.join(projectRoot, 'coverage', 'coverage-final.json');
129
+ if (!existsSync(coveragePath)) {
130
+ return {
131
+ id: 'coverage-evidence',
132
+ status: 'fail',
133
+ summary: 'Coverage evidence is missing.',
134
+ evidence: 'coverage/coverage-final.json',
135
+ };
136
+ }
137
+ try {
138
+ const coverage = JSON.parse(await fs.readFile(coveragePath, 'utf-8'));
139
+ const changedSourceScope = await listChangedSourceScope(projectRoot);
140
+ const touchedCoverage = summarizeCoverage(coverage, projectRoot, changedSourceScope);
141
+ const globalCoverage = summarizeCoverage(coverage, projectRoot);
142
+ const failingMetrics = ['statements', 'lines', 'functions'].filter((metric) => touchedCoverage[metric].percent < COVERAGE_TARGET_PERCENT);
143
+ const branchDisposition = globalCoverage.branches.percent < COVERAGE_TARGET_PERCENT
144
+ ? `; global branches ${globalCoverage.branches.percent}% tracked as ratchet`
145
+ : '';
146
+ const scopeFiles = [...changedSourceScope.keys()].sort();
147
+ const scope = scopeFiles.length > 0 ? `touched:${scopeFiles.join(',')}` : 'global:src';
148
+ return {
149
+ id: 'coverage-evidence',
150
+ status: failingMetrics.length === 0 ? 'pass' : 'fail',
151
+ summary: failingMetrics.length === 0
152
+ ? 'Coverage evidence meets release target for touched source scope.'
153
+ : 'Coverage evidence is below release target for touched source scope.',
154
+ evidence: `${scope}; statements ${touchedCoverage.statements.percent}%, lines ${touchedCoverage.lines.percent}%, functions ${touchedCoverage.functions.percent}%${branchDisposition}`,
155
+ };
156
+ }
157
+ catch (error) {
158
+ return {
159
+ id: 'coverage-evidence',
160
+ status: 'fail',
161
+ summary: 'Coverage evidence could not be parsed.',
162
+ evidence: error instanceof Error ? error.message : String(error),
163
+ };
164
+ }
165
+ }
166
+ export function formatReleaseReadinessReport(report) {
167
+ const lines = [`Release readiness: ${report.status}`, `Generated at: ${report.generated_at}`];
168
+ if (report.blockers.length > 0) {
169
+ lines.push('Blockers:');
170
+ for (const blocker of report.blockers)
171
+ lines.push(`- ${blocker}`);
172
+ }
173
+ if (report.warnings.length > 0) {
174
+ lines.push('Warnings:');
175
+ for (const warning of report.warnings)
176
+ lines.push(`- ${warning}`);
177
+ }
178
+ lines.push('Checks:');
179
+ for (const check of report.checks) {
180
+ lines.push(`- ${check.id}: ${check.status} - ${check.summary}${check.evidence ? ` (${check.evidence})` : ''}`);
181
+ }
182
+ lines.push('CI parity commands:');
183
+ for (const command of report.ci_parity_commands)
184
+ lines.push(`- ${command}`);
185
+ return lines.join('\n');
186
+ }
187
+ async function readPackageJson(projectRoot) {
188
+ const content = await fs.readFile(path.join(projectRoot, 'package.json'), 'utf-8');
189
+ return JSON.parse(content);
190
+ }
191
+ async function listChangedSourceScope(projectRoot) {
192
+ const changed = new Map();
193
+ if (!existsSync(path.join(projectRoot, '.git')))
194
+ return changed;
195
+ try {
196
+ const { stdout: status } = await execFileAsync('git', ['status', '--porcelain=v1', '--untracked-files=all'], {
197
+ cwd: projectRoot,
198
+ maxBuffer: 1024 * 1024,
199
+ });
200
+ for (const line of status.split(/\r?\n/u).filter(Boolean)) {
201
+ const statusCode = line.slice(0, 2);
202
+ const relativePath = line
203
+ .slice(3)
204
+ .trim()
205
+ .split(' -> ')
206
+ .at(-1) || '';
207
+ if (!/^src\/.*\.tsx?$/u.test(relativePath))
208
+ continue;
209
+ changed.set(path.normalize(relativePath), statusCode === '??' ? null : new Set());
210
+ }
211
+ await addDiffSourceLines(projectRoot, changed, ['diff', '--unified=0', '--', 'src']);
212
+ if (changed.size === 0) {
213
+ const upstreamBase = await resolveUpstreamDiffBase(projectRoot);
214
+ if (upstreamBase) {
215
+ await addDiffSourceLines(projectRoot, changed, ['diff', '--unified=0', `${upstreamBase}...HEAD`, '--', 'src']);
216
+ }
217
+ }
218
+ }
219
+ catch {
220
+ return changed;
221
+ }
222
+ return changed;
223
+ }
224
+ async function resolveUpstreamDiffBase(projectRoot) {
225
+ try {
226
+ const { stdout: upstreamStdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'], {
227
+ cwd: projectRoot,
228
+ maxBuffer: 1024 * 1024,
229
+ });
230
+ const upstream = upstreamStdout.trim();
231
+ if (!upstream)
232
+ return undefined;
233
+ const [{ stdout: baseStdout }, { stdout: headStdout }] = await Promise.all([
234
+ execFileAsync('git', ['merge-base', 'HEAD', upstream], {
235
+ cwd: projectRoot,
236
+ maxBuffer: 1024 * 1024,
237
+ }),
238
+ execFileAsync('git', ['rev-parse', 'HEAD'], {
239
+ cwd: projectRoot,
240
+ maxBuffer: 1024 * 1024,
241
+ }),
242
+ ]);
243
+ const base = baseStdout.trim();
244
+ const head = headStdout.trim();
245
+ return base && base !== head ? base : undefined;
246
+ }
247
+ catch {
248
+ return undefined;
249
+ }
250
+ }
251
+ async function addDiffSourceLines(projectRoot, changed, diffArgs) {
252
+ const { stdout: diff } = await execFileAsync('git', diffArgs, {
253
+ cwd: projectRoot,
254
+ maxBuffer: 1024 * 1024,
255
+ });
256
+ let currentFile = '';
257
+ let newLine = 0;
258
+ for (const line of diff.split(/\r?\n/u)) {
259
+ if (line.startsWith('+++ b/')) {
260
+ currentFile = path.normalize(line.slice('+++ b/'.length));
261
+ if (/^src[/\\].*\.tsx?$/u.test(currentFile) && !changed.has(currentFile)) {
262
+ changed.set(currentFile, new Set());
263
+ }
264
+ continue;
265
+ }
266
+ const hunk = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/u);
267
+ if (hunk) {
268
+ newLine = Number(hunk[1]);
269
+ continue;
270
+ }
271
+ if (!currentFile || !changed.has(currentFile) || changed.get(currentFile) === null)
272
+ continue;
273
+ if (line.startsWith('+') && !line.startsWith('+++')) {
274
+ changed.get(currentFile)?.add(newLine);
275
+ newLine += 1;
276
+ }
277
+ else if (!line.startsWith('-')) {
278
+ newLine += 1;
279
+ }
280
+ }
281
+ }
282
+ function summarizeCoverage(coverage, projectRoot, changedSourceScope = new Map()) {
283
+ const totals = {
284
+ statements: { covered: 0, total: 0, percent: 100 },
285
+ branches: { covered: 0, total: 0, percent: 100 },
286
+ functions: { covered: 0, total: 0, percent: 100 },
287
+ lines: { covered: 0, total: 0, percent: 100 },
288
+ };
289
+ for (const [absolutePath, fileCoverage] of Object.entries(coverage)) {
290
+ const relativePath = path.normalize(path.relative(projectRoot, fileCoverage.path || absolutePath));
291
+ const isSource = relativePath.startsWith(`src${path.sep}`) && /\.tsx?$/u.test(relativePath);
292
+ if (!isSource)
293
+ continue;
294
+ const changedLines = changedSourceScope.get(relativePath);
295
+ if (changedSourceScope.size > 0 && changedLines === undefined)
296
+ continue;
297
+ const lineHits = new Map();
298
+ for (const [statementId, count] of Object.entries(fileCoverage.s || {})) {
299
+ const line = fileCoverage.statementMap?.[statementId]?.start?.line;
300
+ if (changedLines && (line === undefined || !changedLines.has(line)))
301
+ continue;
302
+ totals.statements.total += 1;
303
+ if (count > 0)
304
+ totals.statements.covered += 1;
305
+ if (line !== undefined) {
306
+ lineHits.set(line, (lineHits.get(line) || 0) + (count > 0 ? 1 : 0));
307
+ }
308
+ }
309
+ for (const [functionId, count] of Object.entries(fileCoverage.f || {})) {
310
+ const line = fileCoverage.fnMap?.[functionId]?.decl?.start?.line || fileCoverage.fnMap?.[functionId]?.loc?.start?.line;
311
+ if (changedLines && (line === undefined || !changedLines.has(line)))
312
+ continue;
313
+ totals.functions.total += 1;
314
+ if (count > 0)
315
+ totals.functions.covered += 1;
316
+ }
317
+ for (const counts of Object.values(fileCoverage.b || {})) {
318
+ for (const count of counts) {
319
+ totals.branches.total += 1;
320
+ if (count > 0)
321
+ totals.branches.covered += 1;
322
+ }
323
+ }
324
+ for (const count of lineHits.values()) {
325
+ totals.lines.total += 1;
326
+ if (count > 0)
327
+ totals.lines.covered += 1;
328
+ }
329
+ }
330
+ for (const metric of Object.values(totals)) {
331
+ metric.percent = roundPercent(metric.covered, metric.total);
332
+ }
333
+ return totals;
334
+ }
335
+ function roundPercent(covered, total) {
336
+ return total === 0 ? 100 : Number(((covered / total) * 100).toFixed(2));
337
+ }
338
+ async function evaluateNpmConfig(projectRoot) {
339
+ const npmrcPath = path.join(projectRoot, '.npmrc');
340
+ const gitignorePath = path.join(projectRoot, '.gitignore');
341
+ const gitignore = await fs.readFile(gitignorePath, 'utf-8').catch(() => '');
342
+ const npmrcIgnored = gitignore.split(/\r?\n/u).some((line) => line.trim() === '.npmrc');
343
+ if (existsSync(npmrcPath)) {
344
+ return {
345
+ id: 'npmrc-secret-boundary',
346
+ status: 'fail',
347
+ summary: 'Project-local .npmrc exists and must not be present for release readiness.',
348
+ evidence: '.npmrc',
349
+ };
350
+ }
351
+ return {
352
+ id: 'npmrc-secret-boundary',
353
+ status: npmrcIgnored ? 'pass' : 'warn',
354
+ summary: npmrcIgnored ? '.npmrc is absent and ignored.' : '.npmrc is absent but not explicitly ignored.',
355
+ evidence: npmrcIgnored ? undefined : '.gitignore',
356
+ };
357
+ }
358
+ async function evaluatePackageSecurity(projectRoot) {
359
+ const report = await evaluatePackageSecurityGates(projectRoot);
360
+ const issues = [
361
+ ...report.package_allowlist.issues,
362
+ ...report.secret_scan.issues,
363
+ ];
364
+ return {
365
+ id: 'package-security-gates',
366
+ status: report.status === 'pass' ? 'pass' : 'fail',
367
+ summary: report.status === 'pass'
368
+ ? 'Package allowlist and high-confidence secret scan passed.'
369
+ : 'Package allowlist or high-confidence secret scan failed.',
370
+ evidence: issues.length > 0
371
+ ? issues.map((issue) => `${issue.code}:${issue.path}`).join(', ')
372
+ : `${report.package_allowlist.allowed_files.length} package entries; ${report.secret_scan.scanned_files} files scanned`,
373
+ };
374
+ }
375
+ async function evaluateSchemaArtifacts(projectRoot) {
376
+ const missing = REQUIRED_SCHEMA_FILES.filter((fileName) => !existsSync(path.join(projectRoot, 'schemas', 'sdd', fileName)));
377
+ return {
378
+ id: 'schema-artifacts',
379
+ status: missing.length === 0 ? 'pass' : 'fail',
380
+ summary: missing.length === 0 ? 'Required SDD schema artifacts are present.' : 'Required SDD schema artifacts are missing.',
381
+ evidence: missing.join(', ') || REQUIRED_SCHEMA_FILES.join(', '),
382
+ };
383
+ }
384
+ async function evaluateProvenanceAndSbom(projectRoot, packageJson) {
385
+ const workflowPath = path.join(projectRoot, '.github', 'workflows', 'release-prepare.yml');
386
+ const releaseDocPath = path.join(projectRoot, 'docs', 'release.md');
387
+ const securityDocPath = path.join(projectRoot, 'docs', 'security.md');
388
+ const workflow = await fs.readFile(workflowPath, 'utf-8').catch(() => '');
389
+ const releaseDoc = await fs.readFile(releaseDocPath, 'utf-8').catch(() => '');
390
+ const securityDoc = await fs.readFile(securityDocPath, 'utf-8').catch(() => '');
391
+ const docText = `${releaseDoc}\n${securityDoc}`;
392
+ const missing = [];
393
+ if (packageJson.publishConfig?.provenance !== true) {
394
+ missing.push('publishConfig.provenance=true');
395
+ }
396
+ if (!packageJson.scripts?.['release:sbom']) {
397
+ missing.push('scripts.release:sbom');
398
+ }
399
+ if (!/id-token:\s*write/u.test(workflow)) {
400
+ missing.push('release workflow id-token: write');
401
+ }
402
+ if (!/changesets\/action|npm\s+publish/u.test(workflow)) {
403
+ missing.push('release workflow publish step');
404
+ }
405
+ if (!/SBOM|Software Bill of Materials|npm sbom|CycloneDX|SPDX/iu.test(docText)) {
406
+ missing.push('SBOM documentation');
407
+ }
408
+ if (!/trusted publishing|OIDC|provenance/iu.test(docText)) {
409
+ missing.push('trusted publishing/provenance documentation');
410
+ }
411
+ return {
412
+ id: 'provenance-sbom',
413
+ status: missing.length === 0 ? 'pass' : 'fail',
414
+ summary: missing.length === 0
415
+ ? 'Trusted publishing provenance and SBOM evidence are configured.'
416
+ : 'Trusted publishing provenance or SBOM evidence is incomplete.',
417
+ evidence: missing.join(', ') || 'publishConfig.provenance, release:sbom, OIDC workflow, docs/release.md, docs/security.md',
418
+ };
419
+ }
420
+ async function evaluateTarballSmokeAndRollback(projectRoot, packageJson) {
421
+ const workflowPath = path.join(projectRoot, '.github', 'workflows', 'ci.yml');
422
+ const releaseDocPath = path.join(projectRoot, 'docs', 'release.md');
423
+ const workflow = await fs.readFile(workflowPath, 'utf-8').catch(() => '');
424
+ const releaseDoc = await fs.readFile(releaseDocPath, 'utf-8').catch(() => '');
425
+ const missing = [];
426
+ if (!packageJson.scripts?.['check:pack-version']) {
427
+ missing.push('scripts.check:pack-version');
428
+ }
429
+ if (!/pnpm\s+run\s+check:pack-version/u.test(workflow)) {
430
+ missing.push('CI check:pack-version step');
431
+ }
432
+ if (!/(npm|pnpm)\s+pack/u.test(workflow)) {
433
+ missing.push('CI tarball pack step');
434
+ }
435
+ if (!/npm\s+install\s+-g\s+\.\/.*--force/u.test(workflow.replace(/\s+/gu, ' '))) {
436
+ missing.push('CI global tarball install step');
437
+ }
438
+ if (!/codesdd\s+--version/u.test(workflow)) {
439
+ missing.push('CI CLI version smoke');
440
+ }
441
+ if (!/codesdd\s+install\s+--tools\s+none/u.test(workflow)) {
442
+ missing.push('CI bootstrap install smoke');
443
+ }
444
+ if (!/(manual fallback release|tarball)/iu.test(releaseDoc)) {
445
+ missing.push('tarball fallback release docs');
446
+ }
447
+ if (!/npm\s+deprecate\s+@devtrack-solution\/codesdd@<broken-version>/u.test(releaseDoc)) {
448
+ missing.push('npm deprecate rollback command');
449
+ }
450
+ if (!/(rollback strategy|cut a patch release|publish and update release notes)/iu.test(releaseDoc)) {
451
+ missing.push('rollback operator steps');
452
+ }
453
+ return {
454
+ id: 'tarball-smoke-rollback',
455
+ status: missing.length === 0 ? 'pass' : 'fail',
456
+ summary: missing.length === 0
457
+ ? 'Tarball install smoke and rollback evidence are configured.'
458
+ : 'Tarball install smoke or rollback evidence is incomplete.',
459
+ evidence: missing.join(', ') || '.github/workflows/ci.yml, docs/release.md, scripts.check:pack-version',
460
+ };
461
+ }
462
+ async function evaluateReleaseDocs(projectRoot) {
463
+ const required = ['docs/release.md', 'docs/security.md', 'README.md'];
464
+ const missing = required.filter((relativePath) => !existsSync(path.join(projectRoot, relativePath)));
465
+ return {
466
+ id: 'release-docs',
467
+ status: missing.length === 0 ? 'pass' : 'fail',
468
+ summary: missing.length === 0 ? 'Release and security documentation are present.' : 'Release or security documentation is missing.',
469
+ evidence: missing.join(', ') || required.join(', '),
470
+ };
471
+ }
472
+ //# sourceMappingURL=release-readiness.js.map
@@ -0,0 +1,45 @@
1
+ export declare const PROJECT_STATE_DIR_NAME = ".sdd";
2
+ export declare const FORBIDDEN_PROJECT_RUNTIME_DIR_NAME = ".codesdd";
3
+ export type CodesddStorageBoundary = 'project-state' | 'global-config' | 'global-cache' | 'global-runtime' | 'legacy-read-fallback' | 'invalid-project-runtime' | 'external';
4
+ export interface CodesddStorageBoundaryClassification {
5
+ boundary: CodesddStorageBoundary;
6
+ path: string;
7
+ valid: boolean;
8
+ canonical: boolean;
9
+ versioned: boolean;
10
+ cacheable: boolean;
11
+ reason: string;
12
+ }
13
+ export interface CodesddStorageBoundaryReport {
14
+ schema_version: 1;
15
+ project_root: string;
16
+ project_state_dir: string;
17
+ global_runtime_dir: string;
18
+ global_config_path: string;
19
+ global_cache_dir: string;
20
+ global_cache_tiers: Record<string, string>;
21
+ legacy_config_path: string;
22
+ rules: {
23
+ versioned_project_state: string;
24
+ global_runtime: string;
25
+ global_cache: string;
26
+ forbidden_project_runtime: string;
27
+ };
28
+ }
29
+ export interface CodesddStorageBoundaryValidation {
30
+ valid: boolean;
31
+ findings: Array<{
32
+ path: string;
33
+ boundary: CodesddStorageBoundary;
34
+ message: string;
35
+ }>;
36
+ classifications: CodesddStorageBoundaryClassification[];
37
+ }
38
+ export declare function buildCodesddStorageBoundaryReport(projectRoot?: string): CodesddStorageBoundaryReport;
39
+ export declare function classifyCodesddStoragePath(targetPath: string, options?: {
40
+ projectRoot?: string;
41
+ }): CodesddStorageBoundaryClassification;
42
+ export declare function validateCodesddStoragePaths(targetPaths: string[], options?: {
43
+ projectRoot?: string;
44
+ }): CodesddStorageBoundaryValidation;
45
+ //# sourceMappingURL=runtime-boundary-contract.d.ts.map
@@ -0,0 +1,90 @@
1
+ import * as path from 'node:path';
2
+ import { getGlobalCacheDir, getGlobalCacheTierDirs, getGlobalConfigDir, getGlobalConfigPath, getLegacyGlobalConfigDir, getLegacyGlobalConfigPath, } from '../global-config.js';
3
+ export const PROJECT_STATE_DIR_NAME = '.sdd';
4
+ export const FORBIDDEN_PROJECT_RUNTIME_DIR_NAME = '.codesdd';
5
+ export function buildCodesddStorageBoundaryReport(projectRoot = process.cwd()) {
6
+ const normalizedProjectRoot = normalizePath(projectRoot);
7
+ return {
8
+ schema_version: 1,
9
+ project_root: normalizedProjectRoot,
10
+ project_state_dir: path.join(normalizedProjectRoot, PROJECT_STATE_DIR_NAME),
11
+ global_runtime_dir: getGlobalConfigDir(),
12
+ global_config_path: getGlobalConfigPath(),
13
+ global_cache_dir: getGlobalCacheDir(),
14
+ global_cache_tiers: getGlobalCacheTierDirs(),
15
+ legacy_config_path: getLegacyGlobalConfigPath(),
16
+ rules: {
17
+ versioned_project_state: 'Project decisions, backlog state, active/planned/archived features, ADRs, and docs live under .sdd and are versioned with the repository.',
18
+ global_runtime: 'Machine-level config, shell integration, provider settings, and reusable runtime files live under ~/.codesdd and are not repository state.',
19
+ global_cache: 'Cacheable derived data lives under ~/.codesdd/cache with project fingerprint isolation and can be discarded.',
20
+ forbidden_project_runtime: 'A project-local .codesdd directory is not canonical; use .sdd for project state and ~/.codesdd for runtime/cache.',
21
+ },
22
+ };
23
+ }
24
+ export function classifyCodesddStoragePath(targetPath, options = {}) {
25
+ const normalizedPath = normalizePath(targetPath);
26
+ const projectRoot = normalizePath(options.projectRoot ?? process.cwd());
27
+ const projectStateDir = path.join(projectRoot, PROJECT_STATE_DIR_NAME);
28
+ const forbiddenProjectRuntimeDir = path.join(projectRoot, FORBIDDEN_PROJECT_RUNTIME_DIR_NAME);
29
+ const globalConfigPath = normalizePath(getGlobalConfigPath());
30
+ const globalConfigDir = normalizePath(getGlobalConfigDir());
31
+ const globalCacheDir = normalizePath(getGlobalCacheDir());
32
+ const legacyConfigPath = normalizePath(getLegacyGlobalConfigPath());
33
+ const legacyConfigDir = normalizePath(getLegacyGlobalConfigDir());
34
+ if (isSameOrInside(normalizedPath, forbiddenProjectRuntimeDir)) {
35
+ return classification('invalid-project-runtime', normalizedPath, false, false, false, false, 'Project-local .codesdd is not canonical; use .sdd for project state or ~/.codesdd for runtime/cache.');
36
+ }
37
+ if (isSameOrInside(normalizedPath, projectStateDir)) {
38
+ return classification('project-state', normalizedPath, true, true, true, false, 'Versioned project governance, docs, decisions, and lifecycle state belong under .sdd.');
39
+ }
40
+ if (normalizedPath === globalConfigPath) {
41
+ return classification('global-config', normalizedPath, true, true, false, false, 'Machine-level CodeSDD configuration belongs in ~/.codesdd/config.toml and is not project state.');
42
+ }
43
+ if (isSameOrInside(normalizedPath, globalCacheDir)) {
44
+ return classification('global-cache', normalizedPath, true, true, false, true, 'Cacheable derived data belongs under ~/.codesdd/cache and must remain discardable.');
45
+ }
46
+ if (isSameOrInside(normalizedPath, globalConfigDir)) {
47
+ return classification('global-runtime', normalizedPath, true, true, false, false, 'Reusable machine runtime files belong under ~/.codesdd and are not repository state.');
48
+ }
49
+ if (normalizedPath === legacyConfigPath || isSameOrInside(normalizedPath, legacyConfigDir)) {
50
+ return classification('legacy-read-fallback', normalizedPath, true, false, false, false, 'Legacy global config is read only as a compatibility fallback; new runtime state belongs under ~/.codesdd.');
51
+ }
52
+ return classification('external', normalizedPath, true, false, false, false, 'Path is outside CodeSDD project state and global runtime boundaries.');
53
+ }
54
+ export function validateCodesddStoragePaths(targetPaths, options = {}) {
55
+ const classifications = targetPaths.map((targetPath) => classifyCodesddStoragePath(targetPath, options));
56
+ const findings = classifications
57
+ .filter((entry) => !entry.valid)
58
+ .map((entry) => ({
59
+ path: entry.path,
60
+ boundary: entry.boundary,
61
+ message: entry.reason,
62
+ }));
63
+ return {
64
+ valid: findings.length === 0,
65
+ findings,
66
+ classifications,
67
+ };
68
+ }
69
+ function classification(boundary, targetPath, valid, canonical, versioned, cacheable, reason) {
70
+ return {
71
+ boundary,
72
+ path: targetPath,
73
+ valid,
74
+ canonical,
75
+ versioned,
76
+ cacheable,
77
+ reason,
78
+ };
79
+ }
80
+ function normalizePath(input) {
81
+ return path.resolve(input);
82
+ }
83
+ function isSameOrInside(candidate, parent) {
84
+ if (candidate === parent) {
85
+ return true;
86
+ }
87
+ const relative = path.relative(parent, candidate);
88
+ return relative.length > 0 && !relative.startsWith('..') && !path.isAbsolute(relative);
89
+ }
90
+ //# sourceMappingURL=runtime-boundary-contract.js.map