@devtrack-solution/codesdd 1.2.4-rc3 → 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 (128) hide show
  1. package/.sdd/skills/curated/devtrack-api/SKILL.md +91 -12
  2. package/.sdd/skills/curated/devtrack-api/agents/claude-code.yaml +2 -0
  3. package/.sdd/skills/curated/devtrack-api/agents/codex.yaml +2 -0
  4. package/.sdd/skills/curated/devtrack-api/agents/cursor.yaml +2 -0
  5. package/.sdd/skills/curated/devtrack-api/agents/gemini.yaml +2 -0
  6. package/.sdd/skills/curated/devtrack-api/agents/kimi.yaml +2 -0
  7. package/.sdd/skills/curated/devtrack-api/agents/openai.yaml +3 -3
  8. package/.sdd/skills/curated/devtrack-api/agents/opencode.yaml +2 -0
  9. package/.sdd/skills/curated/devtrack-api/references/application-presentation.md +59 -3
  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 +1898 -2
  12. package/.sdd/skills/curated/devtrack-api/references/domain-modeling.md +3 -1
  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 +20 -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 +4 -3
  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 -5
  20. package/README.md +122 -25
  21. package/dist/cli/program.js +180 -11
  22. package/dist/commands/config.js +27 -1
  23. package/dist/commands/sdd/execution.js +64 -2
  24. package/dist/commands/sdd.js +119 -4
  25. package/dist/core/cli/command-matrix.d.ts +18 -0
  26. package/dist/core/cli/command-matrix.js +148 -0
  27. package/dist/core/cli-command-quality.js +2 -0
  28. package/dist/core/config-schema.d.ts +14 -1
  29. package/dist/core/config-schema.js +32 -1
  30. package/dist/core/config.d.ts +1 -0
  31. package/dist/core/config.js +11 -0
  32. package/dist/core/global-config.d.ts +13 -0
  33. package/dist/core/init.d.ts +2 -2
  34. package/dist/core/init.js +13 -14
  35. package/dist/core/sdd/agent-binding.d.ts +9 -9
  36. package/dist/core/sdd/agent-runtime-contract.d.ts +4 -4
  37. package/dist/core/sdd/allocator-recovery.d.ts +14 -0
  38. package/dist/core/sdd/allocator-recovery.js +30 -0
  39. package/dist/core/sdd/allocator-security.d.ts +18 -0
  40. package/dist/core/sdd/allocator-security.js +36 -0
  41. package/dist/core/sdd/api-foundation-baseline.d.ts +111 -0
  42. package/dist/core/sdd/api-foundation-baseline.js +151 -0
  43. package/dist/core/sdd/api-foundation-parity.d.ts +114 -0
  44. package/dist/core/sdd/api-foundation-parity.js +131 -0
  45. package/dist/core/sdd/api-profile-catalog.d.ts +36 -0
  46. package/dist/core/sdd/api-profile-catalog.js +132 -0
  47. package/dist/core/sdd/api-profile-dry-run-projection.d.ts +93 -0
  48. package/dist/core/sdd/api-profile-dry-run-projection.js +370 -0
  49. package/dist/core/sdd/api-profile-recipes.d.ts +82 -0
  50. package/dist/core/sdd/api-profile-recipes.js +484 -0
  51. package/dist/core/sdd/artifact-id-allocator.d.ts +368 -0
  52. package/dist/core/sdd/artifact-id-allocator.js +510 -0
  53. package/dist/core/sdd/check.d.ts +50 -1
  54. package/dist/core/sdd/check.js +286 -9
  55. package/dist/core/sdd/deepagent-contracts.d.ts +4 -4
  56. package/dist/core/sdd/deepagents/reversa-subagents.d.ts +3 -3
  57. package/dist/core/sdd/default-bootstrap-files.d.ts +1 -1
  58. package/dist/core/sdd/default-bootstrap-files.js +0 -2
  59. package/dist/core/sdd/default-skills.js +7 -5
  60. package/dist/core/sdd/devtrack-api-appliance.d.ts +34 -0
  61. package/dist/core/sdd/devtrack-api-appliance.js +138 -34
  62. package/dist/core/sdd/devtrack-api-architecture.d.ts +16 -0
  63. package/dist/core/sdd/devtrack-api-architecture.js +86 -0
  64. package/dist/core/sdd/docs-sync.js +3 -3
  65. package/dist/core/sdd/enterprise-mutating-command-gate.d.ts +27 -0
  66. package/dist/core/sdd/enterprise-mutating-command-gate.js +104 -0
  67. package/dist/core/sdd/enterprise-provenance-gates.d.ts +20 -0
  68. package/dist/core/sdd/enterprise-provenance-gates.js +63 -0
  69. package/dist/core/sdd/enterprise-provisioning-policy.d.ts +26 -0
  70. package/dist/core/sdd/enterprise-provisioning-policy.js +104 -0
  71. package/dist/core/sdd/governance-schemas.d.ts +2 -2
  72. package/dist/core/sdd/governance-schemas.js +11 -2
  73. package/dist/core/sdd/json-schema.js +4 -0
  74. package/dist/core/sdd/legacy-operations.js +93 -4
  75. package/dist/core/sdd/package-security-gates.js +2 -0
  76. package/dist/core/sdd/package-structure-gate.d.ts +85 -3
  77. package/dist/core/sdd/package-structure-gate.js +386 -8
  78. package/dist/core/sdd/parallel-feat-automation.d.ts +6 -6
  79. package/dist/core/sdd/plugin-policy.js +6 -1
  80. package/dist/core/sdd/plugin-registry.d.ts +3 -3
  81. package/dist/core/sdd/quality-validation.d.ts +5 -5
  82. package/dist/core/sdd/release-readiness.d.ts +49 -0
  83. package/dist/core/sdd/release-readiness.js +303 -8
  84. package/dist/core/sdd/reversa-architecture-extractor.d.ts +13 -0
  85. package/dist/core/sdd/reversa-architecture-extractor.js +89 -0
  86. package/dist/core/sdd/reversa-artifact-writer.d.ts +18 -0
  87. package/dist/core/sdd/reversa-artifact-writer.js +40 -0
  88. package/dist/core/sdd/reversa-command-policy.d.ts +136 -0
  89. package/dist/core/sdd/reversa-command-policy.js +361 -0
  90. package/dist/core/sdd/reversa-data-extractor.d.ts +11 -0
  91. package/dist/core/sdd/reversa-data-extractor.js +73 -0
  92. package/dist/core/sdd/reversa-equivalence.d.ts +20 -0
  93. package/dist/core/sdd/reversa-equivalence.js +34 -0
  94. package/dist/core/sdd/reversa-evidence.d.ts +298 -0
  95. package/dist/core/sdd/reversa-evidence.js +118 -0
  96. package/dist/core/sdd/reversa-reconstruction.d.ts +29 -0
  97. package/dist/core/sdd/reversa-reconstruction.js +32 -0
  98. package/dist/core/sdd/reversa-rules-extractor.d.ts +12 -0
  99. package/dist/core/sdd/reversa-rules-extractor.js +86 -0
  100. package/dist/core/sdd/reversa-source-safety.d.ts +19 -0
  101. package/dist/core/sdd/reversa-source-safety.js +105 -0
  102. package/dist/core/sdd/reversa-surface-scout.d.ts +13 -0
  103. package/dist/core/sdd/reversa-surface-scout.js +85 -0
  104. package/dist/core/sdd/reversa-ux-mapper.d.ts +11 -0
  105. package/dist/core/sdd/reversa-ux-mapper.js +73 -0
  106. package/dist/core/sdd/sdk-agent-plugin-quality-gates.d.ts +1 -1
  107. package/dist/core/sdd/services/archive-quality-coherence.service.d.ts +17 -0
  108. package/dist/core/sdd/services/archive-quality-coherence.service.js +141 -0
  109. package/dist/core/sdd/services/decide.service.js +1 -1
  110. package/dist/core/sdd/services/finalize.service.d.ts +2 -0
  111. package/dist/core/sdd/services/finalize.service.js +48 -2
  112. package/dist/core/sdd/services/historical-quality-regression.service.d.ts +35 -0
  113. package/dist/core/sdd/services/historical-quality-regression.service.js +228 -0
  114. package/dist/core/sdd/services/ingest-deposito.service.js +1 -1
  115. package/dist/core/sdd/services/planning-execution-coherence.service.d.ts +45 -0
  116. package/dist/core/sdd/services/planning-execution-coherence.service.js +225 -0
  117. package/dist/core/sdd/state.js +15 -5
  118. package/dist/core/sdd/types.d.ts +3 -3
  119. package/dist/core/sdd/workspace-schemas.d.ts +45 -4
  120. package/dist/core/sdd/workspace-schemas.js +27 -6
  121. package/dist/core/shared/skill-generation.d.ts +2 -0
  122. package/dist/core/shared/skill-generation.js +19 -2
  123. package/dist/core/shared/tool-detection.d.ts +19 -0
  124. package/dist/core/shared/tool-detection.js +89 -0
  125. package/package.json +6 -5
  126. package/schemas/sdd/5-quality.schema.json +43 -0
  127. package/schemas/sdd/reversa-evidence-bundle.schema.json +466 -0
  128. package/schemas/sdd/workspace-catalog.schema.json +511 -0
@@ -2,18 +2,67 @@ export type ReleaseReadinessStatus = 'ready' | 'warning' | 'blocked';
2
2
  export interface ReleaseReadinessCheck {
3
3
  id: string;
4
4
  status: 'pass' | 'warn' | 'fail';
5
+ classification?: ReleaseReadinessClassification;
6
+ owner?: string;
7
+ delta?: string;
8
+ next_action?: string;
9
+ history?: string[];
10
+ exception?: ReleaseReadinessExceptionState;
5
11
  summary: string;
6
12
  evidence?: string;
7
13
  }
14
+ export type ReleaseReadinessClassification = 'passed' | 'failed' | 'excepted' | 'follow_up' | 'not_applicable';
15
+ export interface ReleaseReadinessExceptionState {
16
+ status: 'none' | 'active' | 'stale';
17
+ source?: string;
18
+ scope?: string;
19
+ reason?: string;
20
+ accepted_risk?: string;
21
+ compensating_control?: string;
22
+ review_deadline?: string;
23
+ approver?: string;
24
+ }
8
25
  export interface ReleaseReadinessReport {
9
26
  schema_version: 1;
10
27
  status: ReleaseReadinessStatus;
11
28
  generated_at: string;
29
+ blocker_classification: Array<{
30
+ check_id: string;
31
+ classification: ReleaseReadinessClassification;
32
+ owner: string;
33
+ next_action: string;
34
+ stale_exception: boolean;
35
+ }>;
12
36
  blockers: string[];
13
37
  warnings: string[];
14
38
  checks: ReleaseReadinessCheck[];
39
+ handoff_sections: ReleaseReadinessHandoffSection[];
15
40
  ci_parity_commands: string[];
16
41
  }
42
+ export type ReleaseReadinessHandoffSectionId = 'governance' | 'tests' | 'release_readiness' | 'git_cleanliness' | 'runtime_env';
43
+ export interface ReleaseReadinessHandoffSection {
44
+ id: ReleaseReadinessHandoffSectionId;
45
+ title: string;
46
+ status: ReleaseReadinessStatus;
47
+ owner: string;
48
+ next_action: string;
49
+ checks: ReleaseReadinessCheck[];
50
+ evidence: {
51
+ json_snapshot: {
52
+ section_id: ReleaseReadinessHandoffSectionId;
53
+ status: ReleaseReadinessStatus;
54
+ checks: Array<{
55
+ id: string;
56
+ status: ReleaseReadinessCheck['status'];
57
+ classification: ReleaseReadinessClassification;
58
+ owner: string;
59
+ next_action: string;
60
+ evidence?: string;
61
+ }>;
62
+ };
63
+ human_snapshot: string[];
64
+ };
65
+ }
17
66
  export declare function evaluateReleaseReadiness(projectRoot: string): Promise<ReleaseReadinessReport>;
18
67
  export declare function formatReleaseReadinessReport(report: ReleaseReadinessReport): string;
19
68
  //# sourceMappingURL=release-readiness.d.ts.map
@@ -3,6 +3,7 @@ import { promises as fs } from 'node:fs';
3
3
  import { execFile } from 'node:child_process';
4
4
  import path from 'node:path';
5
5
  import { promisify } from 'node:util';
6
+ import { parse as parseYaml } from 'yaml';
6
7
  import { SddCheckCommand } from './check.js';
7
8
  import { loadProjectSddConfig, loadStateSnapshot, resolveSddPaths, } from './state.js';
8
9
  import { evaluatePackageSecurityGates } from './package-security-gates.js';
@@ -32,6 +33,76 @@ const REQUIRED_SCHEMA_FILES = [
32
33
  'agent-runtime-opencode-run-evidence.schema.json',
33
34
  ];
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
+ };
35
106
  export async function evaluateReleaseReadiness(projectRoot) {
36
107
  const checks = [];
37
108
  const packageJson = await readPackageJson(projectRoot);
@@ -77,19 +148,41 @@ export async function evaluateReleaseReadiness(projectRoot) {
77
148
  checks.push(await evaluateTarballSmokeAndRollback(projectRoot, packageJson));
78
149
  checks.push(await evaluateSchemaArtifacts(projectRoot));
79
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
+ }
80
162
  const blockers = checks
81
- .filter((check) => check.status === 'fail')
82
- .map((check) => `${check.id}: ${check.summary}${check.evidence ? ` (${check.evidence})` : ''}`);
163
+ .filter((check) => check.classification === 'failed')
164
+ .map((check) => `${check.id}: ${check.summary}${check.evidence ? ` (${check.evidence})` : ''} [owner=${check.owner}; next=${check.next_action}]`);
83
165
  const warnings = checks
84
- .filter((check) => check.status === 'warn')
85
- .map((check) => `${check.id}: ${check.summary}${check.evidence ? ` (${check.evidence})` : ''}`);
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);
86
169
  return {
87
170
  schema_version: 1,
88
171
  status: blockers.length > 0 ? 'blocked' : warnings.length > 0 ? 'warning' : 'ready',
89
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
+ })),
90
182
  blockers,
91
183
  warnings,
92
184
  checks,
185
+ handoff_sections: handoffSections,
93
186
  ci_parity_commands: CI_PARITY_COMMANDS,
94
187
  };
95
188
  }
@@ -137,14 +230,22 @@ async function evaluateCoverageEvidence(projectRoot) {
137
230
  try {
138
231
  const coverage = JSON.parse(await fs.readFile(coveragePath, 'utf-8'));
139
232
  const changedSourceScope = await listChangedSourceScope(projectRoot);
140
- const touchedCoverage = summarizeCoverage(coverage, projectRoot, changedSourceScope);
141
233
  const globalCoverage = summarizeCoverage(coverage, projectRoot);
142
- const failingMetrics = ['statements', 'lines', 'functions'].filter((metric) => touchedCoverage[metric].percent < COVERAGE_TARGET_PERCENT);
143
234
  const branchDisposition = globalCoverage.branches.percent < COVERAGE_TARGET_PERCENT
144
235
  ? `; global branches ${globalCoverage.branches.percent}% tracked as ratchet`
145
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);
146
247
  const scopeFiles = [...changedSourceScope.keys()].sort();
147
- const scope = scopeFiles.length > 0 ? `touched:${scopeFiles.join(',')}` : 'global:src';
248
+ const scope = `touched:${scopeFiles.join(',')}`;
148
249
  return {
149
250
  id: 'coverage-evidence',
150
251
  status: failingMetrics.length === 0 ? 'pass' : 'fail',
@@ -165,6 +266,21 @@ async function evaluateCoverageEvidence(projectRoot) {
165
266
  }
166
267
  export function formatReleaseReadinessReport(report) {
167
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
+ }
168
284
  if (report.blockers.length > 0) {
169
285
  lines.push('Blockers:');
170
286
  for (const blocker of report.blockers)
@@ -177,13 +293,192 @@ export function formatReleaseReadinessReport(report) {
177
293
  }
178
294
  lines.push('Checks:');
179
295
  for (const check of report.checks) {
180
- lines.push(`- ${check.id}: ${check.status} - ${check.summary}${check.evidence ? ` (${check.evidence})` : ''}`);
296
+ lines.push(`- ${check.id}: ${check.status}/${check.classification ?? 'passed'} - ${check.summary}${check.evidence ? ` (${check.evidence})` : ''}${check.next_action ? ` [next=${check.next_action}]` : ''}`);
181
297
  }
182
298
  lines.push('CI parity commands:');
183
299
  for (const command of report.ci_parity_commands)
184
300
  lines.push(`- ${command}`);
185
301
  return lines.join('\n');
186
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
+ }
187
482
  async function readPackageJson(projectRoot) {
188
483
  const content = await fs.readFile(path.join(projectRoot, 'package.json'), 'utf-8');
189
484
  return JSON.parse(content);
@@ -0,0 +1,13 @@
1
+ import type { ReversaEvidenceBundle } from './reversa-evidence.js';
2
+ export interface ReversaArchitectureSource {
3
+ path: string;
4
+ content: string;
5
+ }
6
+ export interface BuildReversaArchitectureMapInput {
7
+ featureRef: string;
8
+ operationId: string;
9
+ generatedAt: string;
10
+ sources: ReversaArchitectureSource[];
11
+ }
12
+ export declare function buildReversaArchitectureMap(input: BuildReversaArchitectureMapInput): ReversaEvidenceBundle;
13
+ //# sourceMappingURL=reversa-architecture-extractor.d.ts.map
@@ -0,0 +1,89 @@
1
+ import { attestReversaSource } from './reversa-source-safety.js';
2
+ export function buildReversaArchitectureMap(input) {
3
+ const accepted = input.sources
4
+ .map((source) => ({ source, attestation: attestReversaSource({ sourcePath: source.path, content: source.content }) }))
5
+ .filter((entry) => entry.attestation.status === 'accepted' && entry.attestation.attestation);
6
+ const blockedCount = input.sources.length - accepted.length;
7
+ const modules = accepted.flatMap(({ source, attestation }) => scanModules(attestation.normalized_path, source.content));
8
+ const dependencies = accepted.flatMap(({ source, attestation }) => scanDependencies(attestation.normalized_path, source.content));
9
+ const findings = [];
10
+ if (modules.length > 0) {
11
+ findings.push({
12
+ id: 'REV-FIND-ARCH-MODULES',
13
+ phase: 'architecture',
14
+ title: 'Architecture modules',
15
+ summary: `Detected ${modules.length} module or class boundaries.`,
16
+ confidence: 'medium',
17
+ source_refs: unique(modules.map((entry) => entry.source_ref)),
18
+ contradiction_refs: [],
19
+ promoted_refs: [],
20
+ metadata: { modules },
21
+ });
22
+ }
23
+ if (dependencies.length > 0) {
24
+ findings.push({
25
+ id: 'REV-FIND-ARCH-DEPENDENCIES',
26
+ phase: 'architecture',
27
+ title: 'Dependency imports',
28
+ summary: `Detected ${dependencies.length} import or require dependencies.`,
29
+ confidence: 'medium',
30
+ source_refs: unique(dependencies.map((entry) => entry.source_ref)),
31
+ contradiction_refs: [],
32
+ promoted_refs: [],
33
+ metadata: { dependencies },
34
+ });
35
+ }
36
+ return {
37
+ schema_version: 1,
38
+ contract: 'reversa-evidence-bundle/v1',
39
+ operation_id: input.operationId,
40
+ generated_at: input.generatedAt,
41
+ feature_ref: input.featureRef,
42
+ mode: 'read-only',
43
+ source_attestations: accepted.map((entry) => entry.attestation.attestation),
44
+ findings,
45
+ artifacts: [
46
+ {
47
+ path: `.sdd/evidence/reversa/${input.featureRef}/architecture-map.yaml`,
48
+ artifact_type: 'architecture_map',
49
+ phase: 'architecture',
50
+ produced_by: 'codesdd reversa architecture extractor',
51
+ write_scope: 'read_only',
52
+ },
53
+ ],
54
+ validations: [
55
+ {
56
+ id: 'REV-VAL-ARCH-SAFETY',
57
+ phase: 'architecture',
58
+ command: 'buildReversaArchitectureMap',
59
+ status: blockedCount === 0 ? 'passed' : 'blocked',
60
+ issue_codes: blockedCount === 0 ? [] : ['UNSAFE_SOURCE_BLOCKED'],
61
+ },
62
+ ],
63
+ contradictions: [],
64
+ human_questions: [],
65
+ quality_refs: [`.sdd/active/${input.featureRef}/5-quality.yaml`],
66
+ residual_risks: blockedCount === 0 ? [] : [{
67
+ code: 'UNSAFE_SOURCE_BLOCKED',
68
+ severity: 'medium',
69
+ description: `${blockedCount} source(s) were blocked by Reversa intake safety.`,
70
+ mitigation: 'Provide safe project-relative sources before architecture extraction.',
71
+ }],
72
+ metadata: { module_count: modules.length, dependency_count: dependencies.length },
73
+ };
74
+ }
75
+ function scanModules(sourceRef, content) {
76
+ return [...content.matchAll(/\b(?:class|interface|module|namespace)\s+([A-Za-z0-9_]+)/gu)].map((match) => ({
77
+ name: match[1] ?? 'unknown',
78
+ source_ref: sourceRef,
79
+ }));
80
+ }
81
+ function scanDependencies(sourceRef, content) {
82
+ const imports = [...content.matchAll(/\bimport\b[^'"]*['"]([^'"]+)['"]/gu)].map((match) => match[1] ?? '');
83
+ const requires = [...content.matchAll(/\brequire\(\s*['"]([^'"]+)['"]\s*\)/gu)].map((match) => match[1] ?? '');
84
+ return [...imports, ...requires].filter(Boolean).map((target) => ({ target, source_ref: sourceRef }));
85
+ }
86
+ function unique(values) {
87
+ return [...new Set(values)];
88
+ }
89
+ //# sourceMappingURL=reversa-architecture-extractor.js.map
@@ -0,0 +1,18 @@
1
+ import type { ReversaEvidenceBundle } from './reversa-evidence.js';
2
+ export interface ReversaArtifactWritePlan {
3
+ schema_version: 1;
4
+ contract: 'reversa-artifact-write-plan/v1';
5
+ feature_ref: string;
6
+ status: 'ready' | 'needs_clarification';
7
+ draft_artifacts: Array<{
8
+ artifact_type: 'INS' | 'DEB' | 'EPIC' | 'FEAT' | 'ADR' | 'DOC';
9
+ path: string;
10
+ source_finding_ref: string;
11
+ title: string;
12
+ write_scope: 'draft_only';
13
+ }>;
14
+ human_questions: NonNullable<ReversaEvidenceBundle['human_questions']>;
15
+ blocked_operations: string[];
16
+ }
17
+ export declare function buildReversaArtifactWritePlan(bundle: ReversaEvidenceBundle): ReversaArtifactWritePlan;
18
+ //# sourceMappingURL=reversa-artifact-writer.d.ts.map
@@ -0,0 +1,40 @@
1
+ export function buildReversaArtifactWritePlan(bundle) {
2
+ const lowConfidenceQuestions = bundle.findings
3
+ .filter((finding) => finding.confidence === 'low' || finding.confidence === 'unknown')
4
+ .map((finding) => ({
5
+ id: `REV-Q-${finding.id.replace(/^REV-FIND-/u, '')}`,
6
+ question: `Confirm finding before promotion: ${finding.title}`,
7
+ blocks_phase: 'artifact_generation',
8
+ required_before_apply: true,
9
+ }));
10
+ const humanQuestions = [...bundle.human_questions, ...lowConfidenceQuestions];
11
+ return {
12
+ schema_version: 1,
13
+ contract: 'reversa-artifact-write-plan/v1',
14
+ feature_ref: bundle.feature_ref,
15
+ status: humanQuestions.some((question) => question.required_before_apply) ? 'needs_clarification' : 'ready',
16
+ draft_artifacts: bundle.findings.map((finding) => ({
17
+ artifact_type: inferArtifactType(finding.phase),
18
+ path: `.sdd/evidence/reversa/${bundle.feature_ref}/drafts/${finding.id.toLowerCase()}.yaml`,
19
+ source_finding_ref: finding.id,
20
+ title: finding.title,
21
+ write_scope: 'draft_only',
22
+ })),
23
+ human_questions: humanQuestions,
24
+ blocked_operations: [
25
+ 'write .sdd/state directly',
26
+ 'promote drafts without CodeSDD lifecycle command',
27
+ 'apply generated artifacts before required human questions are resolved',
28
+ ],
29
+ };
30
+ }
31
+ function inferArtifactType(phase) {
32
+ if (phase === 'artifact_generation')
33
+ return 'FEAT';
34
+ if (phase === 'migration' || phase === 'reconstruction' || phase === 'equivalence')
35
+ return 'ADR';
36
+ if (phase === 'rules' || phase === 'architecture')
37
+ return 'DEB';
38
+ return 'DOC';
39
+ }
40
+ //# sourceMappingURL=reversa-artifact-writer.js.map