@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.
- package/.sdd/skills/curated/devtrack-api/SKILL.md +91 -12
- package/.sdd/skills/curated/devtrack-api/agents/claude-code.yaml +2 -0
- package/.sdd/skills/curated/devtrack-api/agents/codex.yaml +2 -0
- package/.sdd/skills/curated/devtrack-api/agents/cursor.yaml +2 -0
- package/.sdd/skills/curated/devtrack-api/agents/gemini.yaml +2 -0
- package/.sdd/skills/curated/devtrack-api/agents/kimi.yaml +2 -0
- package/.sdd/skills/curated/devtrack-api/agents/openai.yaml +3 -3
- package/.sdd/skills/curated/devtrack-api/agents/opencode.yaml +2 -0
- package/.sdd/skills/curated/devtrack-api/references/application-presentation.md +59 -3
- package/.sdd/skills/curated/devtrack-api/references/consumer-sync-policy.md +15 -3
- package/.sdd/skills/curated/devtrack-api/references/contract-pack.yaml +1898 -2
- package/.sdd/skills/curated/devtrack-api/references/domain-modeling.md +3 -1
- package/.sdd/skills/curated/devtrack-api/references/field-validation-protocol.md +40 -0
- package/.sdd/skills/curated/devtrack-api/references/foundation-layout.md +20 -2
- package/.sdd/skills/curated/devtrack-api/references/generated-artifact-invalidation.md +97 -0
- package/.sdd/skills/curated/devtrack-api/references/implementation-checklist.md +30 -1
- package/.sdd/skills/curated/devtrack-api/references/portable-agent-contract.md +4 -3
- package/.sdd/skills/curated/devtrack-api/references/testing-validation.md +22 -1
- package/.sdd/skills/curated/devtrack-api/references/typeorm-infrastructure.md +9 -5
- package/README.md +122 -25
- package/dist/cli/program.js +180 -11
- package/dist/commands/config.js +27 -1
- package/dist/commands/sdd/execution.js +64 -2
- package/dist/commands/sdd.js +119 -4
- package/dist/core/cli/command-matrix.d.ts +18 -0
- package/dist/core/cli/command-matrix.js +148 -0
- package/dist/core/cli-command-quality.js +2 -0
- package/dist/core/config-schema.d.ts +14 -1
- package/dist/core/config-schema.js +32 -1
- package/dist/core/config.d.ts +1 -0
- package/dist/core/config.js +11 -0
- package/dist/core/global-config.d.ts +13 -0
- package/dist/core/init.d.ts +2 -2
- package/dist/core/init.js +13 -14
- package/dist/core/sdd/agent-binding.d.ts +9 -9
- package/dist/core/sdd/agent-runtime-contract.d.ts +4 -4
- package/dist/core/sdd/allocator-recovery.d.ts +14 -0
- package/dist/core/sdd/allocator-recovery.js +30 -0
- package/dist/core/sdd/allocator-security.d.ts +18 -0
- package/dist/core/sdd/allocator-security.js +36 -0
- package/dist/core/sdd/api-foundation-baseline.d.ts +111 -0
- package/dist/core/sdd/api-foundation-baseline.js +151 -0
- package/dist/core/sdd/api-foundation-parity.d.ts +114 -0
- package/dist/core/sdd/api-foundation-parity.js +131 -0
- package/dist/core/sdd/api-profile-catalog.d.ts +36 -0
- package/dist/core/sdd/api-profile-catalog.js +132 -0
- package/dist/core/sdd/api-profile-dry-run-projection.d.ts +93 -0
- package/dist/core/sdd/api-profile-dry-run-projection.js +370 -0
- package/dist/core/sdd/api-profile-recipes.d.ts +82 -0
- package/dist/core/sdd/api-profile-recipes.js +484 -0
- package/dist/core/sdd/artifact-id-allocator.d.ts +368 -0
- package/dist/core/sdd/artifact-id-allocator.js +510 -0
- package/dist/core/sdd/check.d.ts +50 -1
- package/dist/core/sdd/check.js +286 -9
- package/dist/core/sdd/deepagent-contracts.d.ts +4 -4
- package/dist/core/sdd/deepagents/reversa-subagents.d.ts +3 -3
- package/dist/core/sdd/default-bootstrap-files.d.ts +1 -1
- package/dist/core/sdd/default-bootstrap-files.js +0 -2
- package/dist/core/sdd/default-skills.js +7 -5
- package/dist/core/sdd/devtrack-api-appliance.d.ts +34 -0
- package/dist/core/sdd/devtrack-api-appliance.js +138 -34
- package/dist/core/sdd/devtrack-api-architecture.d.ts +16 -0
- package/dist/core/sdd/devtrack-api-architecture.js +86 -0
- package/dist/core/sdd/docs-sync.js +3 -3
- package/dist/core/sdd/enterprise-mutating-command-gate.d.ts +27 -0
- package/dist/core/sdd/enterprise-mutating-command-gate.js +104 -0
- package/dist/core/sdd/enterprise-provenance-gates.d.ts +20 -0
- package/dist/core/sdd/enterprise-provenance-gates.js +63 -0
- package/dist/core/sdd/enterprise-provisioning-policy.d.ts +26 -0
- package/dist/core/sdd/enterprise-provisioning-policy.js +104 -0
- package/dist/core/sdd/governance-schemas.d.ts +2 -2
- package/dist/core/sdd/governance-schemas.js +11 -2
- package/dist/core/sdd/json-schema.js +4 -0
- package/dist/core/sdd/legacy-operations.js +93 -4
- package/dist/core/sdd/package-security-gates.js +2 -0
- package/dist/core/sdd/package-structure-gate.d.ts +85 -3
- package/dist/core/sdd/package-structure-gate.js +386 -8
- package/dist/core/sdd/parallel-feat-automation.d.ts +6 -6
- package/dist/core/sdd/plugin-policy.js +6 -1
- package/dist/core/sdd/plugin-registry.d.ts +3 -3
- package/dist/core/sdd/quality-validation.d.ts +5 -5
- package/dist/core/sdd/release-readiness.d.ts +49 -0
- package/dist/core/sdd/release-readiness.js +303 -8
- package/dist/core/sdd/reversa-architecture-extractor.d.ts +13 -0
- package/dist/core/sdd/reversa-architecture-extractor.js +89 -0
- package/dist/core/sdd/reversa-artifact-writer.d.ts +18 -0
- package/dist/core/sdd/reversa-artifact-writer.js +40 -0
- package/dist/core/sdd/reversa-command-policy.d.ts +136 -0
- package/dist/core/sdd/reversa-command-policy.js +361 -0
- package/dist/core/sdd/reversa-data-extractor.d.ts +11 -0
- package/dist/core/sdd/reversa-data-extractor.js +73 -0
- package/dist/core/sdd/reversa-equivalence.d.ts +20 -0
- package/dist/core/sdd/reversa-equivalence.js +34 -0
- package/dist/core/sdd/reversa-evidence.d.ts +298 -0
- package/dist/core/sdd/reversa-evidence.js +118 -0
- package/dist/core/sdd/reversa-reconstruction.d.ts +29 -0
- package/dist/core/sdd/reversa-reconstruction.js +32 -0
- package/dist/core/sdd/reversa-rules-extractor.d.ts +12 -0
- package/dist/core/sdd/reversa-rules-extractor.js +86 -0
- package/dist/core/sdd/reversa-source-safety.d.ts +19 -0
- package/dist/core/sdd/reversa-source-safety.js +105 -0
- package/dist/core/sdd/reversa-surface-scout.d.ts +13 -0
- package/dist/core/sdd/reversa-surface-scout.js +85 -0
- package/dist/core/sdd/reversa-ux-mapper.d.ts +11 -0
- package/dist/core/sdd/reversa-ux-mapper.js +73 -0
- package/dist/core/sdd/sdk-agent-plugin-quality-gates.d.ts +1 -1
- package/dist/core/sdd/services/archive-quality-coherence.service.d.ts +17 -0
- package/dist/core/sdd/services/archive-quality-coherence.service.js +141 -0
- package/dist/core/sdd/services/decide.service.js +1 -1
- package/dist/core/sdd/services/finalize.service.d.ts +2 -0
- package/dist/core/sdd/services/finalize.service.js +48 -2
- package/dist/core/sdd/services/historical-quality-regression.service.d.ts +35 -0
- package/dist/core/sdd/services/historical-quality-regression.service.js +228 -0
- package/dist/core/sdd/services/ingest-deposito.service.js +1 -1
- package/dist/core/sdd/services/planning-execution-coherence.service.d.ts +45 -0
- package/dist/core/sdd/services/planning-execution-coherence.service.js +225 -0
- package/dist/core/sdd/state.js +15 -5
- package/dist/core/sdd/types.d.ts +3 -3
- package/dist/core/sdd/workspace-schemas.d.ts +45 -4
- package/dist/core/sdd/workspace-schemas.js +27 -6
- package/dist/core/shared/skill-generation.d.ts +2 -0
- package/dist/core/shared/skill-generation.js +19 -2
- package/dist/core/shared/tool-detection.d.ts +19 -0
- package/dist/core/shared/tool-detection.js +89 -0
- package/package.json +6 -5
- package/schemas/sdd/5-quality.schema.json +43 -0
- package/schemas/sdd/reversa-evidence-bundle.schema.json +466 -0
- 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.
|
|
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.
|
|
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 =
|
|
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
|