@devtrack-solution/codesdd 1.2.3 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.sdd/skills/curated/devtrack-api/SKILL.md +98 -12
- package/.sdd/skills/curated/devtrack-api/agents/claude-code.yaml +10 -0
- package/.sdd/skills/curated/devtrack-api/agents/codex.yaml +10 -0
- package/.sdd/skills/curated/devtrack-api/agents/cursor.yaml +10 -0
- package/.sdd/skills/curated/devtrack-api/agents/gemini.yaml +10 -0
- package/.sdd/skills/curated/devtrack-api/agents/kimi.yaml +10 -0
- package/.sdd/skills/curated/devtrack-api/agents/openai.yaml +5 -3
- package/.sdd/skills/curated/devtrack-api/agents/opencode.yaml +12 -0
- package/.sdd/skills/curated/devtrack-api/references/application-presentation.md +61 -5
- package/.sdd/skills/curated/devtrack-api/references/consumer-sync-policy.md +15 -3
- package/.sdd/skills/curated/devtrack-api/references/contract-pack.yaml +1951 -0
- package/.sdd/skills/curated/devtrack-api/references/domain-modeling.md +16 -14
- package/.sdd/skills/curated/devtrack-api/references/field-validation-protocol.md +40 -0
- package/.sdd/skills/curated/devtrack-api/references/foundation-layout.md +19 -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 +42 -0
- package/.sdd/skills/curated/devtrack-api/references/testing-validation.md +22 -1
- package/.sdd/skills/curated/devtrack-api/references/typeorm-infrastructure.md +9 -7
- package/README.md +280 -29
- package/dist/applications/sdd/index.d.ts +16 -0
- package/dist/applications/sdd/index.js +16 -0
- package/dist/cli/program.js +180 -11
- package/dist/commands/config.js +197 -10
- package/dist/commands/sdd/execution.js +408 -16
- package/dist/commands/sdd/plugin.js +5 -0
- package/dist/commands/sdd/shared.d.ts +1 -0
- package/dist/commands/sdd/shared.js +10 -0
- package/dist/commands/sdd.js +157 -7
- package/dist/core/cli/command-matrix.d.ts +18 -0
- package/dist/core/cli/command-matrix.js +157 -0
- package/dist/core/cli-command-quality.js +11 -0
- package/dist/core/completions/command-registry.js +45 -0
- package/dist/core/config-schema.d.ts +31 -1
- package/dist/core/config-schema.js +79 -5
- package/dist/core/config.d.ts +1 -0
- package/dist/core/config.js +11 -0
- package/dist/core/global-config.d.ts +29 -0
- package/dist/core/init.d.ts +2 -2
- package/dist/core/init.js +13 -14
- package/dist/core/sdd/agent-binding.d.ts +19 -19
- package/dist/core/sdd/agent-runtime-contract.d.ts +204 -0
- package/dist/core/sdd/agent-runtime-contract.js +200 -0
- 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 +52 -1
- package/dist/core/sdd/check.js +326 -11
- package/dist/core/sdd/coordination/coordination-adapters.d.ts +15 -8
- package/dist/core/sdd/coordination/coordination-adapters.js +43 -15
- package/dist/core/sdd/coordination/index.d.ts +1 -0
- package/dist/core/sdd/coordination/index.js +1 -0
- package/dist/core/sdd/coordination/redis-runtime.d.ts +131 -0
- package/dist/core/sdd/coordination/redis-runtime.js +698 -0
- package/dist/core/sdd/deepagent-contracts.d.ts +99 -5
- package/dist/core/sdd/deepagent-contracts.js +62 -0
- package/dist/core/sdd/deepagents/reversa-subagents.d.ts +3 -3
- package/dist/core/sdd/default-bootstrap-files.d.ts +2 -2
- package/dist/core/sdd/default-bootstrap-files.js +14 -10
- package/dist/core/sdd/default-skills.js +115 -9
- package/dist/core/sdd/devtrack-api-appliance.d.ts +42 -1
- package/dist/core/sdd/devtrack-api-appliance.js +159 -32
- 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 +24 -18
- package/dist/core/sdd/domain/capability-diff.d.ts +63 -0
- package/dist/core/sdd/domain/capability-diff.js +200 -0
- package/dist/core/sdd/domain/change-safety-guardrails.d.ts +74 -0
- package/dist/core/sdd/domain/change-safety-guardrails.js +333 -0
- package/dist/core/sdd/domain/semantic-intent-classifier.d.ts +29 -0
- package/dist/core/sdd/domain/semantic-intent-classifier.js +117 -0
- 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/foundation-artifact-map-validator.d.ts +16 -0
- package/dist/core/sdd/foundation-artifact-map-validator.js +71 -0
- package/dist/core/sdd/foundation-layer-manifest.d.ts +24 -0
- package/dist/core/sdd/foundation-layer-manifest.js +117 -0
- package/dist/core/sdd/governance-schemas.d.ts +2 -2
- package/dist/core/sdd/governance-schemas.js +11 -2
- package/dist/core/sdd/intent-guard.d.ts +22 -0
- package/dist/core/sdd/intent-guard.js +67 -0
- package/dist/core/sdd/json-schema.js +13 -1
- package/dist/core/sdd/legacy-operations.js +169 -5
- package/dist/core/sdd/migrate-workspace.js +39 -0
- package/dist/core/sdd/package-security-gates.d.ts +21 -0
- package/dist/core/sdd/package-security-gates.js +121 -0
- package/dist/core/sdd/package-structure-gate.d.ts +85 -3
- package/dist/core/sdd/package-structure-gate.js +384 -11
- package/dist/core/sdd/parallel-feat-automation.d.ts +185 -7
- package/dist/core/sdd/parallel-feat-automation.js +212 -0
- package/dist/core/sdd/plugin-broker.d.ts +223 -4
- package/dist/core/sdd/plugin-broker.js +10 -0
- package/dist/core/sdd/plugin-cli.d.ts +30 -0
- package/dist/core/sdd/plugin-cli.js +70 -3
- package/dist/core/sdd/plugin-evidence.d.ts +73 -0
- package/dist/core/sdd/plugin-manifest.d.ts +69 -1
- package/dist/core/sdd/plugin-manifest.js +10 -0
- package/dist/core/sdd/plugin-policy-pack.d.ts +1 -1
- package/dist/core/sdd/plugin-policy.js +6 -1
- package/dist/core/sdd/plugin-registry.d.ts +138 -2
- package/dist/core/sdd/plugin-sdk-contract.d.ts +363 -0
- package/dist/core/sdd/plugin-sdk-contract.js +268 -0
- package/dist/core/sdd/plugin-skill-binding.d.ts +1 -1
- package/dist/core/sdd/quality-validation.d.ts +89 -16
- package/dist/core/sdd/release-readiness.d.ts +68 -0
- package/dist/core/sdd/release-readiness.js +767 -0
- 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/runtime-boundary-contract.d.ts +45 -0
- package/dist/core/sdd/runtime-boundary-contract.js +90 -0
- package/dist/core/sdd/sdk-agent-plugin-quality-gates.d.ts +150 -0
- package/dist/core/sdd/sdk-agent-plugin-quality-gates.js +258 -0
- package/dist/core/sdd/services/agent-run.service.d.ts +38 -6
- package/dist/core/sdd/services/agent-run.service.js +73 -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/capability-diff.service.d.ts +18 -0
- package/dist/core/sdd/services/capability-diff.service.js +26 -0
- package/dist/core/sdd/services/change-safety-preflight.service.d.ts +17 -0
- package/dist/core/sdd/services/change-safety-preflight.service.js +17 -0
- package/dist/core/sdd/services/context.service.d.ts +43 -340
- package/dist/core/sdd/services/context.service.js +323 -9
- package/dist/core/sdd/services/decide.service.js +1 -1
- package/dist/core/sdd/services/finalize.service.d.ts +27 -0
- package/dist/core/sdd/services/finalize.service.js +226 -18
- package/dist/core/sdd/services/frontend-impact.service.d.ts +1 -1
- 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/services/semantic-intent-classifier.service.d.ts +6 -0
- package/dist/core/sdd/services/semantic-intent-classifier.service.js +7 -0
- package/dist/core/sdd/state.d.ts +1 -0
- package/dist/core/sdd/state.js +266 -34
- package/dist/core/sdd/store/sdd-stores.js +2 -2
- package/dist/core/sdd/structural-health.d.ts +13 -13
- package/dist/core/sdd/types.d.ts +30 -15
- package/dist/core/sdd/types.js +4 -0
- package/dist/core/sdd/views.js +17 -0
- package/dist/core/sdd/workspace-schemas.d.ts +428 -7
- package/dist/core/sdd/workspace-schemas.js +223 -70
- 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/dist/domains/sdd/index.d.ts +6 -0
- package/dist/domains/sdd/index.js +6 -0
- package/dist/infrastructures/sdd/index.d.ts +7 -0
- package/dist/infrastructures/sdd/index.js +6 -0
- package/dist/presentations/cli/sdd/index.d.ts +3 -0
- package/dist/presentations/cli/sdd/index.js +3 -0
- package/dist/shared/sdd/index.d.ts +3 -0
- package/dist/shared/sdd/index.js +2 -0
- package/package.json +14 -10
- package/schemas/sdd/2-plan.schema.json +207 -2
- package/schemas/sdd/5-quality.schema.json +324 -25
- package/schemas/sdd/agent-runtime-command-plan.schema.json +212 -0
- package/schemas/sdd/agent-runtime-opencode-run-evidence.schema.json +270 -0
- package/schemas/sdd/codesdd-plugin.schema.json +171 -0
- package/schemas/sdd/deepagent-run-request.schema.json +316 -0
- package/schemas/sdd/parallel-feat-automation-plan.schema.json +89 -0
- package/schemas/sdd/parallel-feat-scheduler-request.schema.json +116 -0
- package/schemas/sdd/parallel-feat-scheduler-result.schema.json +404 -0
- package/schemas/sdd/plugin-artifact-manifest.schema.json +109 -0
- package/schemas/sdd/plugin-artifact-map.schema.json +223 -0
- package/schemas/sdd/plugin-evidence-manifest.schema.json +109 -0
- package/schemas/sdd/plugin-language-runtime.schema.json +103 -0
- package/schemas/sdd/plugin-package-governance.schema.json +74 -0
- package/schemas/sdd/plugin-registry.schema.json +171 -0
- package/schemas/sdd/plugin-runtime-invocation-plan.schema.json +109 -0
- package/schemas/sdd/quality-evidence-bundle.schema.json +109 -0
- package/schemas/sdd/reversa-evidence-bundle.schema.json +466 -0
- package/schemas/sdd/sdk-agent-plugin-quality-gate-input.schema.json +168 -0
- package/schemas/sdd/sdk-agent-plugin-quality-gate-report.schema.json +160 -0
- package/schemas/sdd/workspace-catalog.schema.json +5298 -1409
package/dist/core/sdd/check.js
CHANGED
|
@@ -4,13 +4,14 @@ import path from 'node:path';
|
|
|
4
4
|
import { parse as parseYaml } from 'yaml';
|
|
5
5
|
import { CLI_NAME } from '../branding.js';
|
|
6
6
|
import { ID_PATTERNS, } from './types.js';
|
|
7
|
-
import { loadProjectSddConfig, loadStateSnapshot, resolveSddPaths, } from './state.js';
|
|
7
|
+
import { buildDefaultSkillRoutingState, loadProjectSddConfig, loadStateSnapshot, resolveSddPaths, } from './state.js';
|
|
8
8
|
import { DEFAULT_CURATED_SKILL_CATALOG } from './default-skills.js';
|
|
9
9
|
import { renderViews } from './views.js';
|
|
10
10
|
import { syncSddGuideDocs, validateSddGuideDocs } from './docs-sync.js';
|
|
11
11
|
import { evaluateWorkspaceTraceability } from './domain/traceability.js';
|
|
12
12
|
import { detectArchiveArchivedLifecycleCollisions } from './domain/post-active-validation.js';
|
|
13
13
|
import { parseWorkspaceYamlDocument, workspaceTasksSchema, } from './workspace-schemas.js';
|
|
14
|
+
import { scanArchiveQualityCoherence } from './services/archive-quality-coherence.service.js';
|
|
14
15
|
export function checkUniqueIds(items, scope, errors) {
|
|
15
16
|
const seen = new Set();
|
|
16
17
|
for (const item of items) {
|
|
@@ -21,6 +22,7 @@ export function checkUniqueIds(items, scope, errors) {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
const FORBIDDEN_TITLE_PATTERNS = ['debate:', 'insight:', '(preencher', '(placeholder'];
|
|
25
|
+
const CLOSED_BACKLOG_STATUSES = new Set(['DONE', 'ARCHIVED']);
|
|
24
26
|
export function hasForbiddenTitleToken(title) {
|
|
25
27
|
const lowered = title.toLowerCase();
|
|
26
28
|
for (const token of FORBIDDEN_TITLE_PATTERNS) {
|
|
@@ -29,6 +31,258 @@ export function hasForbiddenTitleToken(title) {
|
|
|
29
31
|
}
|
|
30
32
|
return null;
|
|
31
33
|
}
|
|
34
|
+
export function summarizeFrontendGapHealth(gaps) {
|
|
35
|
+
let open = 0;
|
|
36
|
+
let planned = 0;
|
|
37
|
+
let inProgress = 0;
|
|
38
|
+
let resolved = 0;
|
|
39
|
+
let superseded = 0;
|
|
40
|
+
for (const gap of gaps) {
|
|
41
|
+
if (gap.status === 'OPEN')
|
|
42
|
+
open++;
|
|
43
|
+
if (gap.status === 'PLANNED')
|
|
44
|
+
planned++;
|
|
45
|
+
if (gap.status === 'IN_PROGRESS')
|
|
46
|
+
inProgress++;
|
|
47
|
+
if (gap.status === 'DONE')
|
|
48
|
+
resolved++;
|
|
49
|
+
if (gap.status === 'SUPERSEDED')
|
|
50
|
+
superseded++;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
total: gaps.length,
|
|
54
|
+
open,
|
|
55
|
+
planned,
|
|
56
|
+
in_progress: inProgress,
|
|
57
|
+
resolved,
|
|
58
|
+
superseded,
|
|
59
|
+
unresolved: gaps.length - resolved - superseded,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function summarizeLockDomainHealth(items) {
|
|
63
|
+
const lockOwners = new Map();
|
|
64
|
+
for (const item of items) {
|
|
65
|
+
for (const lock of item.lock_domains) {
|
|
66
|
+
const owners = lockOwners.get(lock) || [];
|
|
67
|
+
owners.push(item);
|
|
68
|
+
lockOwners.set(lock, owners);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const shared = [];
|
|
72
|
+
for (const [lock, owners] of lockOwners.entries()) {
|
|
73
|
+
if (owners.length <= 1)
|
|
74
|
+
continue;
|
|
75
|
+
const activeOwners = owners.filter((owner) => !CLOSED_BACKLOG_STATUSES.has(owner.status));
|
|
76
|
+
const historicalOwners = owners.filter((owner) => CLOSED_BACKLOG_STATUSES.has(owner.status));
|
|
77
|
+
const severity = activeOwners.length > 1 ? 'active' : activeOwners.length === 1 ? 'mixed' : 'historical';
|
|
78
|
+
shared.push({
|
|
79
|
+
lock,
|
|
80
|
+
severity,
|
|
81
|
+
owners: owners.map((owner) => owner.id),
|
|
82
|
+
active_owners: activeOwners.map((owner) => owner.id),
|
|
83
|
+
historical_owners: historicalOwners.map((owner) => owner.id),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
shared.sort((left, right) => left.lock.localeCompare(right.lock));
|
|
87
|
+
return {
|
|
88
|
+
shared_total: shared.length,
|
|
89
|
+
active_conflicts: shared.filter((item) => item.severity === 'active').length,
|
|
90
|
+
mixed_history: shared.filter((item) => item.severity === 'mixed').length,
|
|
91
|
+
historical: shared.filter((item) => item.severity === 'historical').length,
|
|
92
|
+
shared,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function normalizeCatalogPath(input) {
|
|
96
|
+
return input.trim().replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '');
|
|
97
|
+
}
|
|
98
|
+
export function hasRepoMapReference(repoPath, repoMapPaths) {
|
|
99
|
+
const normalizedRepoPath = normalizeCatalogPath(repoPath);
|
|
100
|
+
if (!normalizedRepoPath)
|
|
101
|
+
return false;
|
|
102
|
+
return repoMapPaths.some((repoMapPath) => {
|
|
103
|
+
const normalizedRepoMapPath = normalizeCatalogPath(repoMapPath);
|
|
104
|
+
if (!normalizedRepoMapPath)
|
|
105
|
+
return false;
|
|
106
|
+
return (normalizedRepoMapPath === normalizedRepoPath ||
|
|
107
|
+
normalizedRepoMapPath.startsWith(`${normalizedRepoPath}/`) ||
|
|
108
|
+
normalizedRepoPath.startsWith(`${normalizedRepoMapPath}/`));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export function hasIntegrationContract(contractRef, knownContracts) {
|
|
112
|
+
const normalizedContractRef = contractRef.trim();
|
|
113
|
+
if (!normalizedContractRef)
|
|
114
|
+
return false;
|
|
115
|
+
if (knownContracts.has(normalizedContractRef))
|
|
116
|
+
return true;
|
|
117
|
+
for (const knownContract of knownContracts) {
|
|
118
|
+
if (knownContract.startsWith(`${normalizedContractRef}::`)) {
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
export function auditServiceCatalogMaturity(services, backlogItems, repoMapItems, integrationContracts) {
|
|
125
|
+
const backlogIds = new Set(backlogItems.map((item) => item.id));
|
|
126
|
+
const repoMapPaths = repoMapItems.map((item) => item.path);
|
|
127
|
+
const contracts = new Set(integrationContracts.map((item) => item.trim()).filter(Boolean));
|
|
128
|
+
const byLevel = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
|
|
129
|
+
const below5 = [];
|
|
130
|
+
for (const service of services) {
|
|
131
|
+
const unmet = [];
|
|
132
|
+
const hasIdentity = Boolean(service.id.trim()) && Boolean(service.name.trim()) && Boolean((service.responsibility || '').trim());
|
|
133
|
+
if (!hasIdentity)
|
|
134
|
+
unmet.push('identity');
|
|
135
|
+
const hasOwners = service.owner_refs.length > 0 && service.owner_refs.every((ownerRef) => backlogIds.has(ownerRef));
|
|
136
|
+
if (!hasOwners)
|
|
137
|
+
unmet.push('owner_refs');
|
|
138
|
+
const hasRepoPaths = service.repo_paths.length > 0 && service.repo_paths.every((repoPath) => hasRepoMapReference(repoPath, repoMapPaths));
|
|
139
|
+
if (!hasRepoPaths)
|
|
140
|
+
unmet.push('repo_paths');
|
|
141
|
+
const hasContracts = service.contracts.length > 0 &&
|
|
142
|
+
service.contracts.every((contractRef) => hasIntegrationContract(contractRef, contracts));
|
|
143
|
+
if (!hasContracts)
|
|
144
|
+
unmet.push('contracts');
|
|
145
|
+
const hasExternalDependencies = Array.isArray(service.external_dependencies);
|
|
146
|
+
if (!hasExternalDependencies)
|
|
147
|
+
unmet.push('external_dependencies');
|
|
148
|
+
const level = !hasIdentity ? 1 : !hasOwners ? 2 : !hasRepoPaths ? 3 : !hasContracts || !hasExternalDependencies ? 4 : 5;
|
|
149
|
+
byLevel[level] += 1;
|
|
150
|
+
if (level < 5) {
|
|
151
|
+
below5.push({
|
|
152
|
+
id: service.id,
|
|
153
|
+
level,
|
|
154
|
+
unmet,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
serviceCount: services.length,
|
|
160
|
+
byLevel,
|
|
161
|
+
below5,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export async function validateAdrConsistency(adrsDir, backlogItems, errors, warnings, isStrict) {
|
|
165
|
+
const backlogStatus = new Map();
|
|
166
|
+
for (const item of backlogItems) {
|
|
167
|
+
backlogStatus.set(item.id, item.status);
|
|
168
|
+
}
|
|
169
|
+
let filenames;
|
|
170
|
+
try {
|
|
171
|
+
filenames = await fs.readdir(adrsDir);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const adrs = filenames.filter((f) => f.startsWith('ADR-') && f.endsWith('.md'));
|
|
177
|
+
for (const filename of adrs) {
|
|
178
|
+
let content;
|
|
179
|
+
try {
|
|
180
|
+
content = await fs.readFile(path.join(adrsDir, filename), 'utf-8');
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const frontmatterMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(content);
|
|
186
|
+
if (!frontmatterMatch)
|
|
187
|
+
continue;
|
|
188
|
+
let frontmatter;
|
|
189
|
+
try {
|
|
190
|
+
frontmatter = parseYaml(frontmatterMatch[1]) ?? {};
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const adrStatus = typeof frontmatter.status === 'string' ? frontmatter.status : '';
|
|
196
|
+
const featureId = typeof frontmatter.feature_id === 'string' ? frontmatter.feature_id : '';
|
|
197
|
+
const adrId = typeof frontmatter.id === 'string' ? frontmatter.id : filename;
|
|
198
|
+
if (!featureId || !adrStatus)
|
|
199
|
+
continue;
|
|
200
|
+
const featureStatus = backlogStatus.get(featureId);
|
|
201
|
+
if (!featureStatus)
|
|
202
|
+
continue;
|
|
203
|
+
const featureDone = featureStatus === 'DONE' || featureStatus === 'ARCHIVED';
|
|
204
|
+
const adrUnresolved = adrStatus === 'IN_PROGRESS' || adrStatus === 'DRAFT';
|
|
205
|
+
if (featureDone && adrUnresolved) {
|
|
206
|
+
const message = `ADR consistency gate failed: ${adrId} status is ${adrStatus} but feature ${featureId} is ${featureStatus} in backlog`;
|
|
207
|
+
if (isStrict) {
|
|
208
|
+
errors.push(message);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
warnings.push(message);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export function validateInsightMinimums(insightTitle, insightBody, errors) {
|
|
217
|
+
if ((insightTitle || '').trim().length < 50) {
|
|
218
|
+
errors.push(`INSIGHT quality gate: title must be >=50 characters (current: ${(insightTitle || '').trim().length}). Expand the title to describe the problem and expected impact.`);
|
|
219
|
+
}
|
|
220
|
+
const bodyLower = (insightBody || '').toLowerCase();
|
|
221
|
+
const missingKeywords = [];
|
|
222
|
+
if (!/\bproblem|\bproblema|\bissue\b/.test(bodyLower))
|
|
223
|
+
missingKeywords.push('problem description');
|
|
224
|
+
if (!/\bscope|\bescopo|\bboundary\b/.test(bodyLower))
|
|
225
|
+
missingKeywords.push('scope definition');
|
|
226
|
+
if (!/\bimpact|\bimpacto|\boutcome\b/.test(bodyLower))
|
|
227
|
+
missingKeywords.push('expected impact');
|
|
228
|
+
if (missingKeywords.length > 0) {
|
|
229
|
+
errors.push(`INSIGHT quality gate: body must describe problem, scope, and impact. Missing: ${missingKeywords.join(', ')}.`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
export function validateEpicMinimums(epicBody, epicTitle, errors) {
|
|
233
|
+
const bodyClean = (epicBody || '').replace(/^---[\s\S]*?---\r?\n?/, '').trim();
|
|
234
|
+
if (bodyClean.length < 500) {
|
|
235
|
+
errors.push(`EPIC quality gate: expanded plan must be >=500 characters (current: ${bodyClean.length}). Add feature breakdown, dependency map, risk matrix, and DoD.`);
|
|
236
|
+
}
|
|
237
|
+
const featMatches = bodyClean.match(/FEAT-\d{4}/g) || [];
|
|
238
|
+
const uniqueFeats = new Set(featMatches).size;
|
|
239
|
+
if (uniqueFeats < 3) {
|
|
240
|
+
errors.push(`EPIC quality gate: must reference >=3 estimated features (current: ${uniqueFeats}). Expand the breakdown plan with concrete feature estimates.`);
|
|
241
|
+
}
|
|
242
|
+
const hasDependencyMap = /\bupstream\b|\bdownstream\b|\bdependencies\b|\bdependency map\b/i.test(bodyClean);
|
|
243
|
+
if (!hasDependencyMap) {
|
|
244
|
+
errors.push(`EPIC quality gate: must include a dependency map with upstream and downstream references.`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
export function validateTransitionLogConsistency(transitionLogEvents, backlogItems, errors, warnings, isStrict) {
|
|
248
|
+
if (transitionLogEvents.length === 0)
|
|
249
|
+
return;
|
|
250
|
+
const oldestEventTs = Math.min(...transitionLogEvents.map((e) => e.timestamp ? Date.parse(String(e.timestamp)) : Infinity));
|
|
251
|
+
const backlogStatus = new Map(backlogItems.map((item) => [item.id, item.status]));
|
|
252
|
+
const finalizeEvents = new Set();
|
|
253
|
+
for (const event of transitionLogEvents) {
|
|
254
|
+
const action = event.action || event.to || '';
|
|
255
|
+
if (action === 'finalized' || action === 'archived' || action === 'DONE' || action === 'ARCHIVED') {
|
|
256
|
+
finalizeEvents.add(event.entity_id);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const item of backlogItems) {
|
|
260
|
+
const isDone = item.status === 'DONE' || item.status === 'ARCHIVED';
|
|
261
|
+
if (!isDone)
|
|
262
|
+
continue;
|
|
263
|
+
if (finalizeEvents.has(item.id))
|
|
264
|
+
continue;
|
|
265
|
+
const itemDoneAt = item.done_at || item.archived_at || '';
|
|
266
|
+
if (itemDoneAt && oldestEventTs !== Infinity && Date.parse(itemDoneAt) < oldestEventTs) {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const message = `Transition-log consistency: ${item.id} is ${item.status} in backlog but has no finalized event in transition-log`;
|
|
270
|
+
warnings.push(message);
|
|
271
|
+
}
|
|
272
|
+
for (const eventId of finalizeEvents) {
|
|
273
|
+
const status = backlogStatus.get(eventId);
|
|
274
|
+
if (!status) {
|
|
275
|
+
warnings.push(`Transition-log consistency: transition-log has finalized event for ${eventId} but no matching backlog item`);
|
|
276
|
+
}
|
|
277
|
+
else if (status !== 'DONE' && status !== 'ARCHIVED') {
|
|
278
|
+
const message = `Transition-log consistency: ${eventId} has finalized event in transition-log but backlog status is ${status}`;
|
|
279
|
+
if (isStrict)
|
|
280
|
+
errors.push(message);
|
|
281
|
+
else
|
|
282
|
+
warnings.push(message);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
32
286
|
export function validateDiscoveryRecords(records, errors) {
|
|
33
287
|
for (const record of records) {
|
|
34
288
|
if (record.type === 'INS' && !ID_PATTERNS.insight.test(record.id)) {
|
|
@@ -53,7 +307,6 @@ export function validateDiscoveryRecords(records, errors) {
|
|
|
53
307
|
}
|
|
54
308
|
export function validateBacklog(items, errors, warnings) {
|
|
55
309
|
const ids = new Set(items.map((item) => item.id));
|
|
56
|
-
const lockOwners = new Map();
|
|
57
310
|
for (const item of items) {
|
|
58
311
|
const forbidden = hasForbiddenTitleToken(item.title);
|
|
59
312
|
if (forbidden) {
|
|
@@ -67,15 +320,10 @@ export function validateBacklog(items, errors, warnings) {
|
|
|
67
320
|
warnings.push(`Item de backlog ${item.id} bloqueado por referencia inexistente: ${dep}`);
|
|
68
321
|
}
|
|
69
322
|
}
|
|
70
|
-
for (const lock of item.lock_domains) {
|
|
71
|
-
const owners = lockOwners.get(lock) || [];
|
|
72
|
-
owners.push(item.id);
|
|
73
|
-
lockOwners.set(lock, owners);
|
|
74
|
-
}
|
|
75
323
|
}
|
|
76
|
-
for (const
|
|
77
|
-
if (
|
|
78
|
-
warnings.push(`Lock domain "${lock}" compartilhado por: ${
|
|
324
|
+
for (const sharedLock of summarizeLockDomainHealth(items).shared) {
|
|
325
|
+
if (sharedLock.severity === 'active') {
|
|
326
|
+
warnings.push(`Lock domain "${sharedLock.lock}" compartilhado por FEATs ativas: ${sharedLock.active_owners.join(', ')}`);
|
|
79
327
|
}
|
|
80
328
|
}
|
|
81
329
|
}
|
|
@@ -122,6 +370,13 @@ export function validateReferentialIntegrity(snapshot, errors, warnings, isStric
|
|
|
122
370
|
}
|
|
123
371
|
}
|
|
124
372
|
}
|
|
373
|
+
if (snapshot.finalizeQueue?.history) {
|
|
374
|
+
for (const fq of snapshot.finalizeQueue.history) {
|
|
375
|
+
if (!backlogIds.has(fq.feature_id)) {
|
|
376
|
+
record(`Finalize history has entry for feature_id="${fq.feature_id}" blocked by an unknown feature.`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
125
380
|
if (snapshot.unblockEvents?.events) {
|
|
126
381
|
for (const ev of snapshot.unblockEvents.events) {
|
|
127
382
|
if (!backlogIds.has(ev.feature_id)) {
|
|
@@ -172,6 +427,26 @@ export function validateSkillOperationalIntegrity(snapshot, errors) {
|
|
|
172
427
|
}
|
|
173
428
|
}
|
|
174
429
|
}
|
|
430
|
+
const currentRoutes = new Map((snapshot.skillRouting?.routes || []).map((route) => [route.domain, route]));
|
|
431
|
+
for (const defaultRoute of buildDefaultSkillRoutingState().routes) {
|
|
432
|
+
const currentRoute = currentRoutes.get(defaultRoute.domain);
|
|
433
|
+
if (!currentRoute) {
|
|
434
|
+
errors.push(`Default skill route "${defaultRoute.domain}" is missing from skill-routing.yaml`);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const currentSkills = currentRoute.skills || [];
|
|
438
|
+
const currentBundles = currentRoute.bundles || [];
|
|
439
|
+
for (const skillId of defaultRoute.skills) {
|
|
440
|
+
if (!currentSkills.includes(skillId)) {
|
|
441
|
+
errors.push(`Default skill route "${defaultRoute.domain}" is missing skill "${skillId}"`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (const bundleId of defaultRoute.bundles) {
|
|
445
|
+
if (!currentBundles.includes(bundleId)) {
|
|
446
|
+
errors.push(`Default skill route "${defaultRoute.domain}" is missing bundle "${bundleId}"`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
175
450
|
}
|
|
176
451
|
export function computeGraphSummary(items) {
|
|
177
452
|
const byId = new Map(items.map((item) => [item.id, item]));
|
|
@@ -541,6 +816,8 @@ export class SddCheckCommand {
|
|
|
541
816
|
backlog: 0,
|
|
542
817
|
techDebt: 0,
|
|
543
818
|
finalizeQueue: 0,
|
|
819
|
+
finalizeQueuePending: 0,
|
|
820
|
+
finalizeQueueDone: 0,
|
|
544
821
|
frontendEnabled: false,
|
|
545
822
|
frontendGaps: 0,
|
|
546
823
|
frontendRoutes: 0,
|
|
@@ -618,6 +895,8 @@ export class SddCheckCommand {
|
|
|
618
895
|
backlog: 0,
|
|
619
896
|
techDebt: 0,
|
|
620
897
|
finalizeQueue: 0,
|
|
898
|
+
finalizeQueuePending: 0,
|
|
899
|
+
finalizeQueueDone: 0,
|
|
621
900
|
frontendEnabled: config.frontend.enabled,
|
|
622
901
|
frontendGaps: 0,
|
|
623
902
|
frontendRoutes: 0,
|
|
@@ -658,6 +937,8 @@ export class SddCheckCommand {
|
|
|
658
937
|
backlog: 0,
|
|
659
938
|
techDebt: 0,
|
|
660
939
|
finalizeQueue: 0,
|
|
940
|
+
finalizeQueuePending: 0,
|
|
941
|
+
finalizeQueueDone: 0,
|
|
661
942
|
frontendEnabled: config.frontend.enabled,
|
|
662
943
|
frontendGaps: 0,
|
|
663
944
|
frontendRoutes: 0,
|
|
@@ -706,6 +987,15 @@ export class SddCheckCommand {
|
|
|
706
987
|
validatePrivacySourceRegistry(snapshot, warnings);
|
|
707
988
|
await validatePrivacyWorkspaceChecklist(paths.activeDir, snapshot.backlog.items, warnings);
|
|
708
989
|
await validateTraceabilityWorkspaceChecklist(paths.projectRoot, paths.activeDir, snapshot.backlog.items, warnings);
|
|
990
|
+
await validateAdrConsistency(path.join(paths.coreDir, 'adrs'), snapshot.backlog.items, errors, warnings, isStrict);
|
|
991
|
+
validateTransitionLogConsistency(snapshot.transitionLog?.events || [], snapshot.backlog.items, errors, warnings, isStrict);
|
|
992
|
+
const archiveQualityCoherence = await scanArchiveQualityCoherence(paths.archivedDir);
|
|
993
|
+
for (const issue of archiveQualityCoherence.issues) {
|
|
994
|
+
errors.push(issue.message);
|
|
995
|
+
}
|
|
996
|
+
if (archiveQualityCoherence.grandfathered > 0) {
|
|
997
|
+
warnings.push(`Archive quality coherence grandfathered ${archiveQualityCoherence.grandfathered} archived FEAT workspaces (<= FEAT-0451).`);
|
|
998
|
+
}
|
|
709
999
|
const featuresMissingFrontendDeclaration = config.frontend.enabled
|
|
710
1000
|
? snapshot.backlog.items
|
|
711
1001
|
.filter((item) => item.status !== 'ARCHIVED' && item.status !== 'DONE')
|
|
@@ -747,6 +1037,22 @@ export class SddCheckCommand {
|
|
|
747
1037
|
if (snapshot.repoMap.items.length === 0) {
|
|
748
1038
|
missingArchitectureFields.push('repo-map.items vazio');
|
|
749
1039
|
}
|
|
1040
|
+
const serviceCatalogMaturityAudit = auditServiceCatalogMaturity(snapshot.serviceCatalog.services, snapshot.backlog.items, snapshot.repoMap.items, snapshot.integrationContracts.contracts);
|
|
1041
|
+
if (serviceCatalogMaturityAudit.below5.length > 0) {
|
|
1042
|
+
const sample = serviceCatalogMaturityAudit.below5
|
|
1043
|
+
.slice(0, 10)
|
|
1044
|
+
.map((entry) => `${entry.id}(L${entry.level}: ${entry.unmet.join(', ')})`)
|
|
1045
|
+
.join('; ');
|
|
1046
|
+
const remainder = serviceCatalogMaturityAudit.below5.length - Math.min(10, serviceCatalogMaturityAudit.below5.length);
|
|
1047
|
+
const suffix = remainder > 0 ? `; +${remainder} service(s)` : '';
|
|
1048
|
+
const message = `Service catalog maturity gate failed: ${serviceCatalogMaturityAudit.below5.length}/${serviceCatalogMaturityAudit.serviceCount} services are below level 5. ${sample}${suffix}`;
|
|
1049
|
+
if (isStrict) {
|
|
1050
|
+
errors.push(message);
|
|
1051
|
+
}
|
|
1052
|
+
else {
|
|
1053
|
+
warnings.push(message);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
750
1056
|
const hasFrontendDecisionDemand = config.frontend.enabled &&
|
|
751
1057
|
((snapshot.frontendGaps?.items.length ?? 0) > 0 ||
|
|
752
1058
|
snapshot.backlog.items.some((item) => featureHasMetadataFrontendEvidence(item)));
|
|
@@ -796,6 +1102,11 @@ export class SddCheckCommand {
|
|
|
796
1102
|
featuresMissingFgapLink.length === 0;
|
|
797
1103
|
const qualityContractSync = featuresMissingQualityContract.length === 0 &&
|
|
798
1104
|
featuresMissingQualityArtifact.length === 0;
|
|
1105
|
+
const finalizeQueuePending = snapshot.finalizeQueue.items.filter((item) => item.status === 'PENDING').length;
|
|
1106
|
+
const finalizeQueueDone = snapshot.finalizeQueue.history.length +
|
|
1107
|
+
snapshot.finalizeQueue.items.filter((item) => item.status === 'DONE').length;
|
|
1108
|
+
const frontendGapsSummary = summarizeFrontendGapHealth(snapshot.frontendGaps?.items ?? []);
|
|
1109
|
+
const lockDomainHealth = summarizeLockDomainHealth(snapshot.backlog.items);
|
|
799
1110
|
return {
|
|
800
1111
|
valid: errors.length === 0,
|
|
801
1112
|
errors,
|
|
@@ -804,15 +1115,19 @@ export class SddCheckCommand {
|
|
|
804
1115
|
discovery: snapshot.discoveryIndex.records.length,
|
|
805
1116
|
backlog: snapshot.backlog.items.length,
|
|
806
1117
|
techDebt: snapshot.techDebt.items.length,
|
|
807
|
-
finalizeQueue:
|
|
1118
|
+
finalizeQueue: finalizeQueuePending,
|
|
1119
|
+
finalizeQueuePending,
|
|
1120
|
+
finalizeQueueDone,
|
|
808
1121
|
frontendEnabled: config.frontend.enabled,
|
|
809
1122
|
frontendGaps: snapshot.frontendGaps?.items.length ?? 0,
|
|
1123
|
+
frontendGapsSummary,
|
|
810
1124
|
frontendRoutes: snapshot.frontendMap?.routes.length ?? 0,
|
|
811
1125
|
progress_global: progress.progressGlobal,
|
|
812
1126
|
progress_by_radar: progress.progressByRadar,
|
|
813
1127
|
ready_for_parallel: graph.readyForParallel,
|
|
814
1128
|
blocked: graph.blocked,
|
|
815
1129
|
lock_conflicts: graph.lockConflicts,
|
|
1130
|
+
lock_domain_health: lockDomainHealth,
|
|
816
1131
|
documentation_sync: docsValidation.documentationSync,
|
|
817
1132
|
core_views_stale: coreViewsStale,
|
|
818
1133
|
missing_architecture_fields: missingArchitectureFields,
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type AcquireOptions } from '../state-lock.js';
|
|
2
|
+
import { type GlobalConfig } from '../../global-config.js';
|
|
3
|
+
import { type RedisRuntimeConfig, type RedisRuntimeStatus } from './redis-runtime.js';
|
|
4
|
+
export { resolveRedisBoundaryConfig } from './redis-runtime.js';
|
|
2
5
|
export interface CoordinationLockAdapter {
|
|
3
6
|
withLock<T>(scope: string, fn: () => Promise<T>, options?: AcquireOptions): Promise<T>;
|
|
4
7
|
}
|
|
@@ -24,20 +27,16 @@ export interface CoordinationEventBusAdapter {
|
|
|
24
27
|
publish(event: CoordinationEvent): Promise<void>;
|
|
25
28
|
drain(): Promise<CoordinationEvent[]>;
|
|
26
29
|
}
|
|
27
|
-
export
|
|
28
|
-
requested: boolean;
|
|
29
|
-
namespace: string;
|
|
30
|
-
url?: string;
|
|
31
|
-
}
|
|
30
|
+
export type RedisBoundaryConfig = RedisRuntimeConfig;
|
|
32
31
|
export interface SddCoordinationAdapters {
|
|
33
|
-
mode: 'filesystem';
|
|
32
|
+
mode: 'filesystem' | 'redis' | 'hybrid';
|
|
34
33
|
cacheTier: {
|
|
35
34
|
root: string;
|
|
36
35
|
projectFingerprint: string;
|
|
37
36
|
namespace: string;
|
|
38
37
|
};
|
|
39
38
|
redis: RedisBoundaryConfig & {
|
|
40
|
-
status:
|
|
39
|
+
status: RedisRuntimeStatus;
|
|
41
40
|
reason: string;
|
|
42
41
|
};
|
|
43
42
|
locks: CoordinationLockAdapter;
|
|
@@ -100,12 +99,20 @@ export declare class InMemoryCoordinationEventBus implements CoordinationEventBu
|
|
|
100
99
|
publish(event: CoordinationEvent): Promise<void>;
|
|
101
100
|
drain(): Promise<CoordinationEvent[]>;
|
|
102
101
|
}
|
|
103
|
-
export declare function resolveRedisBoundaryConfig(env?: NodeJS.ProcessEnv): RedisBoundaryConfig;
|
|
104
102
|
export declare function createFilesystemFirstCoordinationAdapters(options: {
|
|
105
103
|
stateDir: string;
|
|
106
104
|
projectRoot?: string;
|
|
107
105
|
configFingerprint?: string;
|
|
108
106
|
cacheRootDir?: string;
|
|
109
107
|
env?: NodeJS.ProcessEnv;
|
|
108
|
+
globalConfig?: Partial<GlobalConfig>;
|
|
110
109
|
}): SddCoordinationAdapters;
|
|
110
|
+
export declare function createSddCoordinationAdapters(options: {
|
|
111
|
+
stateDir: string;
|
|
112
|
+
projectRoot?: string;
|
|
113
|
+
configFingerprint?: string;
|
|
114
|
+
cacheRootDir?: string;
|
|
115
|
+
env?: NodeJS.ProcessEnv;
|
|
116
|
+
globalConfig?: Partial<GlobalConfig>;
|
|
117
|
+
}): Promise<SddCoordinationAdapters>;
|
|
111
118
|
//# sourceMappingURL=coordination-adapters.d.ts.map
|
|
@@ -3,6 +3,8 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { promises as fs } from 'node:fs';
|
|
5
5
|
import { createProjectFingerprint, ensureGlobalCacheLayout, getGlobalCacheDir } from '../../global-config.js';
|
|
6
|
+
import { buildRedisOperationalReport, RedisClientFactory, RedisCoordinationCache, RedisEventBusAdapter, RedisLockAdapter, RedisQueueAdapter, resolveRedisBoundaryConfig, } from './redis-runtime.js';
|
|
7
|
+
export { resolveRedisBoundaryConfig } from './redis-runtime.js';
|
|
6
8
|
export class FileSystemLockAdapter {
|
|
7
9
|
stateDir;
|
|
8
10
|
constructor(stateDir) {
|
|
@@ -186,21 +188,11 @@ export class InMemoryCoordinationEventBus {
|
|
|
186
188
|
return this.events.splice(0, this.events.length);
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
|
-
export function resolveRedisBoundaryConfig(env = process.env) {
|
|
190
|
-
const url = env.CODESDD_REDIS_URL?.trim() || env.REDIS_URL?.trim() || undefined;
|
|
191
|
-
const requestedByFlag = env.CODESDD_REDIS_ENABLED === 'true';
|
|
192
|
-
const namespace = env.CODESDD_REDIS_NAMESPACE?.trim() || 'codesdd';
|
|
193
|
-
return {
|
|
194
|
-
requested: Boolean(url || requestedByFlag),
|
|
195
|
-
namespace,
|
|
196
|
-
url,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
191
|
export function createFilesystemFirstCoordinationAdapters(options) {
|
|
200
192
|
if (!options.cacheRootDir) {
|
|
201
193
|
ensureGlobalCacheLayout();
|
|
202
194
|
}
|
|
203
|
-
const redis = resolveRedisBoundaryConfig(options.env);
|
|
195
|
+
const redis = resolveRedisBoundaryConfig(options.env, options.globalConfig);
|
|
204
196
|
const cache = new MultiTierCoordinationCache({
|
|
205
197
|
projectRoot: options.projectRoot ?? process.cwd(),
|
|
206
198
|
configFingerprint: options.configFingerprint,
|
|
@@ -211,10 +203,12 @@ export function createFilesystemFirstCoordinationAdapters(options) {
|
|
|
211
203
|
cacheTier: cache.describeTierState(),
|
|
212
204
|
redis: {
|
|
213
205
|
...redis,
|
|
214
|
-
status: redis.requested ? 'requested-unavailable' : 'disabled',
|
|
215
|
-
reason: redis.
|
|
216
|
-
? 'Redis
|
|
217
|
-
:
|
|
206
|
+
status: redis.validationErrors.length > 0 ? 'blocked' : redis.requested ? 'requested-unavailable' : 'disabled',
|
|
207
|
+
reason: redis.validationErrors.length > 0
|
|
208
|
+
? 'Redis configuration is invalid and was blocked fail-closed.'
|
|
209
|
+
: redis.requested
|
|
210
|
+
? 'Redis is requested but unavailable; filesystem-first defaults remain authoritative.'
|
|
211
|
+
: 'Redis is optional and disabled; filesystem-first defaults are authoritative.',
|
|
218
212
|
},
|
|
219
213
|
locks: new FileSystemLockAdapter(options.stateDir),
|
|
220
214
|
cache,
|
|
@@ -222,4 +216,38 @@ export function createFilesystemFirstCoordinationAdapters(options) {
|
|
|
222
216
|
events: new InMemoryCoordinationEventBus(),
|
|
223
217
|
};
|
|
224
218
|
}
|
|
219
|
+
export async function createSddCoordinationAdapters(options) {
|
|
220
|
+
const filesystem = createFilesystemFirstCoordinationAdapters(options);
|
|
221
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
222
|
+
const redis = resolveRedisBoundaryConfig(options.env, options.globalConfig);
|
|
223
|
+
const report = await buildRedisOperationalReport({
|
|
224
|
+
env: options.env,
|
|
225
|
+
globalConfig: options.globalConfig,
|
|
226
|
+
});
|
|
227
|
+
if (report.status !== 'ready' || !redis.url) {
|
|
228
|
+
return {
|
|
229
|
+
...filesystem,
|
|
230
|
+
redis: {
|
|
231
|
+
...redis,
|
|
232
|
+
status: report.status,
|
|
233
|
+
reason: report.reason,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const projectFingerprint = createProjectFingerprint(projectRoot);
|
|
238
|
+
const factory = new RedisClientFactory(redis);
|
|
239
|
+
return {
|
|
240
|
+
...filesystem,
|
|
241
|
+
mode: redis.fallback === 'filesystem' ? 'hybrid' : 'redis',
|
|
242
|
+
redis: {
|
|
243
|
+
...redis,
|
|
244
|
+
status: 'ready',
|
|
245
|
+
reason: report.reason,
|
|
246
|
+
},
|
|
247
|
+
cache: new RedisCoordinationCache(factory, filesystem.cache, projectFingerprint),
|
|
248
|
+
locks: new RedisLockAdapter(factory, filesystem.locks, projectFingerprint),
|
|
249
|
+
queues: new RedisQueueAdapter(factory, filesystem.queues, projectFingerprint),
|
|
250
|
+
events: new RedisEventBusAdapter(factory, filesystem.events, projectFingerprint),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
225
253
|
//# sourceMappingURL=coordination-adapters.js.map
|