@devtrack-solution/codesdd 1.2.3 → 1.2.4-rc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/.sdd/skills/curated/devtrack-api/SKILL.md +12 -5
  2. package/.sdd/skills/curated/devtrack-api/agents/claude-code.yaml +8 -0
  3. package/.sdd/skills/curated/devtrack-api/agents/codex.yaml +8 -0
  4. package/.sdd/skills/curated/devtrack-api/agents/cursor.yaml +8 -0
  5. package/.sdd/skills/curated/devtrack-api/agents/gemini.yaml +8 -0
  6. package/.sdd/skills/curated/devtrack-api/agents/kimi.yaml +8 -0
  7. package/.sdd/skills/curated/devtrack-api/agents/openai.yaml +4 -2
  8. package/.sdd/skills/curated/devtrack-api/agents/opencode.yaml +10 -0
  9. package/.sdd/skills/curated/devtrack-api/references/application-presentation.md +2 -2
  10. package/.sdd/skills/curated/devtrack-api/references/contract-pack.yaml +55 -0
  11. package/.sdd/skills/curated/devtrack-api/references/domain-modeling.md +13 -13
  12. package/.sdd/skills/curated/devtrack-api/references/foundation-layout.md +2 -3
  13. package/.sdd/skills/curated/devtrack-api/references/implementation-checklist.md +1 -1
  14. package/.sdd/skills/curated/devtrack-api/references/portable-agent-contract.md +41 -0
  15. package/.sdd/skills/curated/devtrack-api/references/typeorm-infrastructure.md +7 -9
  16. package/README.md +159 -5
  17. package/dist/applications/sdd/index.d.ts +16 -0
  18. package/dist/applications/sdd/index.js +16 -0
  19. package/dist/commands/config.js +171 -10
  20. package/dist/commands/sdd/execution.js +345 -15
  21. package/dist/commands/sdd/plugin.js +5 -0
  22. package/dist/commands/sdd/shared.d.ts +1 -0
  23. package/dist/commands/sdd/shared.js +10 -0
  24. package/dist/commands/sdd.js +38 -3
  25. package/dist/core/cli/command-matrix.js +9 -0
  26. package/dist/core/cli-command-quality.js +9 -0
  27. package/dist/core/completions/command-registry.js +45 -0
  28. package/dist/core/config-schema.d.ts +18 -1
  29. package/dist/core/config-schema.js +48 -5
  30. package/dist/core/global-config.d.ts +16 -0
  31. package/dist/core/sdd/agent-binding.d.ts +10 -10
  32. package/dist/core/sdd/agent-runtime-contract.d.ts +204 -0
  33. package/dist/core/sdd/agent-runtime-contract.js +200 -0
  34. package/dist/core/sdd/check.d.ts +2 -0
  35. package/dist/core/sdd/check.js +40 -2
  36. package/dist/core/sdd/coordination/coordination-adapters.d.ts +15 -8
  37. package/dist/core/sdd/coordination/coordination-adapters.js +43 -15
  38. package/dist/core/sdd/coordination/index.d.ts +1 -0
  39. package/dist/core/sdd/coordination/index.js +1 -0
  40. package/dist/core/sdd/coordination/redis-runtime.d.ts +131 -0
  41. package/dist/core/sdd/coordination/redis-runtime.js +698 -0
  42. package/dist/core/sdd/deepagent-contracts.d.ts +98 -4
  43. package/dist/core/sdd/deepagent-contracts.js +62 -0
  44. package/dist/core/sdd/default-bootstrap-files.d.ts +2 -2
  45. package/dist/core/sdd/default-bootstrap-files.js +14 -8
  46. package/dist/core/sdd/default-skills.js +108 -4
  47. package/dist/core/sdd/devtrack-api-appliance.d.ts +8 -1
  48. package/dist/core/sdd/devtrack-api-appliance.js +46 -23
  49. package/dist/core/sdd/docs-sync.js +21 -15
  50. package/dist/core/sdd/domain/capability-diff.d.ts +63 -0
  51. package/dist/core/sdd/domain/capability-diff.js +200 -0
  52. package/dist/core/sdd/domain/change-safety-guardrails.d.ts +74 -0
  53. package/dist/core/sdd/domain/change-safety-guardrails.js +333 -0
  54. package/dist/core/sdd/domain/semantic-intent-classifier.d.ts +29 -0
  55. package/dist/core/sdd/domain/semantic-intent-classifier.js +117 -0
  56. package/dist/core/sdd/foundation-artifact-map-validator.d.ts +16 -0
  57. package/dist/core/sdd/foundation-artifact-map-validator.js +71 -0
  58. package/dist/core/sdd/foundation-layer-manifest.d.ts +24 -0
  59. package/dist/core/sdd/foundation-layer-manifest.js +117 -0
  60. package/dist/core/sdd/intent-guard.d.ts +22 -0
  61. package/dist/core/sdd/intent-guard.js +67 -0
  62. package/dist/core/sdd/json-schema.js +9 -1
  63. package/dist/core/sdd/legacy-operations.js +76 -1
  64. package/dist/core/sdd/migrate-workspace.js +39 -0
  65. package/dist/core/sdd/package-security-gates.d.ts +21 -0
  66. package/dist/core/sdd/package-security-gates.js +119 -0
  67. package/dist/core/sdd/package-structure-gate.js +3 -8
  68. package/dist/core/sdd/parallel-feat-automation.d.ts +181 -3
  69. package/dist/core/sdd/parallel-feat-automation.js +212 -0
  70. package/dist/core/sdd/plugin-broker.d.ts +223 -4
  71. package/dist/core/sdd/plugin-broker.js +10 -0
  72. package/dist/core/sdd/plugin-cli.d.ts +30 -0
  73. package/dist/core/sdd/plugin-cli.js +70 -3
  74. package/dist/core/sdd/plugin-evidence.d.ts +73 -0
  75. package/dist/core/sdd/plugin-manifest.d.ts +69 -1
  76. package/dist/core/sdd/plugin-manifest.js +10 -0
  77. package/dist/core/sdd/plugin-policy-pack.d.ts +1 -1
  78. package/dist/core/sdd/plugin-registry.d.ts +141 -5
  79. package/dist/core/sdd/plugin-sdk-contract.d.ts +363 -0
  80. package/dist/core/sdd/plugin-sdk-contract.js +268 -0
  81. package/dist/core/sdd/plugin-skill-binding.d.ts +1 -1
  82. package/dist/core/sdd/quality-validation.d.ts +84 -11
  83. package/dist/core/sdd/release-readiness.d.ts +19 -0
  84. package/dist/core/sdd/release-readiness.js +472 -0
  85. package/dist/core/sdd/runtime-boundary-contract.d.ts +45 -0
  86. package/dist/core/sdd/runtime-boundary-contract.js +90 -0
  87. package/dist/core/sdd/sdk-agent-plugin-quality-gates.d.ts +150 -0
  88. package/dist/core/sdd/sdk-agent-plugin-quality-gates.js +258 -0
  89. package/dist/core/sdd/services/agent-run.service.d.ts +38 -6
  90. package/dist/core/sdd/services/agent-run.service.js +73 -1
  91. package/dist/core/sdd/services/capability-diff.service.d.ts +18 -0
  92. package/dist/core/sdd/services/capability-diff.service.js +26 -0
  93. package/dist/core/sdd/services/change-safety-preflight.service.d.ts +17 -0
  94. package/dist/core/sdd/services/change-safety-preflight.service.js +17 -0
  95. package/dist/core/sdd/services/context.service.d.ts +43 -340
  96. package/dist/core/sdd/services/context.service.js +323 -9
  97. package/dist/core/sdd/services/finalize.service.d.ts +25 -0
  98. package/dist/core/sdd/services/finalize.service.js +178 -16
  99. package/dist/core/sdd/services/frontend-impact.service.d.ts +1 -1
  100. package/dist/core/sdd/services/semantic-intent-classifier.service.d.ts +6 -0
  101. package/dist/core/sdd/services/semantic-intent-classifier.service.js +7 -0
  102. package/dist/core/sdd/state.d.ts +1 -0
  103. package/dist/core/sdd/state.js +251 -29
  104. package/dist/core/sdd/store/sdd-stores.js +2 -2
  105. package/dist/core/sdd/structural-health.d.ts +13 -13
  106. package/dist/core/sdd/types.d.ts +27 -12
  107. package/dist/core/sdd/types.js +4 -0
  108. package/dist/core/sdd/views.js +17 -0
  109. package/dist/core/sdd/workspace-schemas.d.ts +387 -7
  110. package/dist/core/sdd/workspace-schemas.js +196 -64
  111. package/dist/domains/sdd/index.d.ts +6 -0
  112. package/dist/domains/sdd/index.js +6 -0
  113. package/dist/infrastructures/sdd/index.d.ts +7 -0
  114. package/dist/infrastructures/sdd/index.js +6 -0
  115. package/dist/presentations/cli/sdd/index.d.ts +3 -0
  116. package/dist/presentations/cli/sdd/index.js +3 -0
  117. package/dist/shared/sdd/index.d.ts +3 -0
  118. package/dist/shared/sdd/index.js +2 -0
  119. package/package.json +9 -6
  120. package/schemas/sdd/2-plan.schema.json +207 -2
  121. package/schemas/sdd/5-quality.schema.json +281 -25
  122. package/schemas/sdd/agent-runtime-command-plan.schema.json +212 -0
  123. package/schemas/sdd/agent-runtime-opencode-run-evidence.schema.json +270 -0
  124. package/schemas/sdd/codesdd-plugin.schema.json +171 -0
  125. package/schemas/sdd/deepagent-run-request.schema.json +316 -0
  126. package/schemas/sdd/parallel-feat-automation-plan.schema.json +89 -0
  127. package/schemas/sdd/parallel-feat-scheduler-request.schema.json +116 -0
  128. package/schemas/sdd/parallel-feat-scheduler-result.schema.json +404 -0
  129. package/schemas/sdd/plugin-artifact-manifest.schema.json +109 -0
  130. package/schemas/sdd/plugin-artifact-map.schema.json +223 -0
  131. package/schemas/sdd/plugin-evidence-manifest.schema.json +109 -0
  132. package/schemas/sdd/plugin-language-runtime.schema.json +103 -0
  133. package/schemas/sdd/plugin-package-governance.schema.json +74 -0
  134. package/schemas/sdd/plugin-registry.schema.json +171 -0
  135. package/schemas/sdd/plugin-runtime-invocation-plan.schema.json +109 -0
  136. package/schemas/sdd/quality-evidence-bundle.schema.json +109 -0
  137. package/schemas/sdd/sdk-agent-plugin-quality-gate-input.schema.json +168 -0
  138. package/schemas/sdd/sdk-agent-plugin-quality-gate-report.schema.json +160 -0
  139. package/schemas/sdd/workspace-catalog.schema.json +3776 -398
@@ -0,0 +1,200 @@
1
+ import { toJSONSchema, z } from 'zod';
2
+ const JSON_SCHEMA_DRAFT = 'https://json-schema.org/draft/2020-12/schema';
3
+ const FEATURE_REF_PATTERN = /^FEAT-\d{4}$/;
4
+ export const agentRuntimeProviderSchema = z.enum(['deepagents', 'codex', 'opencode']);
5
+ export const agentRuntimeModeSchema = z.enum(['read-only', 'plan', 'validate', 'apply-sandbox', 'apply-approved']);
6
+ export const agentRuntimeSandboxSchema = z.enum(['read-only', 'workspace-write', 'danger-full-access']);
7
+ export const agentRuntimeStatusSchema = z.enum(['planned', 'blocked']);
8
+ export const opencodeExecutionStatusSchema = z.enum([
9
+ 'planned',
10
+ 'skipped',
11
+ 'blocked',
12
+ 'running',
13
+ 'completed',
14
+ 'failed',
15
+ ]);
16
+ const agentRuntimeCommandSchema = z.object({
17
+ executable: z.string().min(1),
18
+ args: z.array(z.string()).default([]),
19
+ stdin: z.string().optional(),
20
+ });
21
+ const agentRuntimeEvidenceArtifactSchema = z.object({
22
+ kind: z.string().min(1),
23
+ ref: z.string().min(1),
24
+ path: z.string().min(1).optional(),
25
+ sha256: z.string().min(1).optional(),
26
+ });
27
+ const agentRuntimeRiskSchema = z.object({
28
+ id: z.string().min(1),
29
+ severity: z.enum(['low', 'medium', 'high', 'critical']),
30
+ summary: z.string().min(1),
31
+ mitigation: z.string().min(1).optional(),
32
+ });
33
+ export const agentRuntimeCommandPlanRequestSchema = z.object({
34
+ schema_version: z.literal(1).default(1),
35
+ feature_ref: z.string().regex(FEATURE_REF_PATTERN),
36
+ provider: agentRuntimeProviderSchema,
37
+ mode: agentRuntimeModeSchema.default('plan'),
38
+ instruction: z.string().min(1),
39
+ cwd: z.string().min(1).default('.'),
40
+ model: z.string().min(1).optional(),
41
+ sandbox: agentRuntimeSandboxSchema.default('read-only'),
42
+ output_schema: z.string().min(1).optional(),
43
+ output_last_message: z.string().min(1).optional(),
44
+ agent: z.string().min(1).optional(),
45
+ session: z.string().min(1).optional(),
46
+ files: z.array(z.string().min(1)).default([]),
47
+ approval_grants: z.array(z.enum(['maintainer', 'architecture-board', 'security'])).default([]),
48
+ });
49
+ export const agentRuntimeCommandPlanSchema = z.object({
50
+ schema_version: z.literal(1),
51
+ created_at: z.string().datetime(),
52
+ status: agentRuntimeStatusSchema,
53
+ request: agentRuntimeCommandPlanRequestSchema,
54
+ provider: agentRuntimeProviderSchema,
55
+ command: agentRuntimeCommandSchema.optional(),
56
+ evidence_contract: z.enum(['agent-runtime-v2/deepagents', 'agent-runtime-v2/codex-exec', 'agent-runtime-v2/opencode-run']),
57
+ structured_output: z.boolean(),
58
+ reasons: z.array(z.string()).default([]),
59
+ policy: z.object({
60
+ direct_state_write_allowed: z.literal(false),
61
+ requires_codesdd_finalize: z.literal(true),
62
+ mutating_modes_require_approval: z.literal(true),
63
+ }),
64
+ });
65
+ export const opencodeExecutionEvidenceSchema = z.object({
66
+ schema_version: z.literal(1),
67
+ contract: z.literal('agent-runtime-v2/opencode-run'),
68
+ provider: z.literal('opencode'),
69
+ feature_ref: z.string().regex(FEATURE_REF_PATTERN),
70
+ run_id: z.string().min(1),
71
+ created_at: z.string().datetime(),
72
+ mode: agentRuntimeModeSchema,
73
+ status: opencodeExecutionStatusSchema,
74
+ command: agentRuntimeCommandSchema.extend({
75
+ executable: z.literal('opencode'),
76
+ }),
77
+ command_plan_ref: z.string().min(1).optional(),
78
+ cwd: z.string().min(1).default('.'),
79
+ exit_code: z.number().int().nullable().default(null),
80
+ started_at: z.string().datetime().optional(),
81
+ completed_at: z.string().datetime().optional(),
82
+ duration_ms: z.number().int().nonnegative().optional(),
83
+ structured_output: z.record(z.string(), z.unknown()).default({}),
84
+ stdout_excerpt: z.string().max(8000).optional(),
85
+ stderr_excerpt: z.string().max(8000).optional(),
86
+ artifacts: z.array(agentRuntimeEvidenceArtifactSchema).default([]),
87
+ validations: z.array(z.string().min(1)).default([]),
88
+ redactions: z.array(z.string().min(1)).default([]),
89
+ risks: z.array(agentRuntimeRiskSchema).default([]),
90
+ policy: z.object({
91
+ direct_state_write_allowed: z.literal(false),
92
+ requires_codesdd_finalize: z.literal(true),
93
+ raw_secret_output_allowed: z.literal(false),
94
+ transcript_storage: z.enum(['forbidden', 'redacted-excerpts-only']).default('redacted-excerpts-only'),
95
+ }),
96
+ finalize_intent: z.boolean().default(false),
97
+ });
98
+ export function buildAgentRuntimeCommandPlan(request, createdAt = new Date().toISOString()) {
99
+ const parsed = agentRuntimeCommandPlanRequestSchema.parse(request);
100
+ const reasons = collectPolicyReasons(parsed);
101
+ const status = reasons.length > 0 ? 'blocked' : 'planned';
102
+ const command = status === 'planned' ? buildCommand(parsed) : undefined;
103
+ return agentRuntimeCommandPlanSchema.parse({
104
+ schema_version: 1,
105
+ created_at: createdAt,
106
+ status,
107
+ request: parsed,
108
+ provider: parsed.provider,
109
+ command,
110
+ evidence_contract: evidenceContract(parsed.provider),
111
+ structured_output: true,
112
+ reasons,
113
+ policy: {
114
+ direct_state_write_allowed: false,
115
+ requires_codesdd_finalize: true,
116
+ mutating_modes_require_approval: true,
117
+ },
118
+ });
119
+ }
120
+ export function buildOpenCodeExecutionEvidence(evidence) {
121
+ return opencodeExecutionEvidenceSchema.parse(evidence);
122
+ }
123
+ export function buildAgentRuntimeJsonSchemas() {
124
+ return {
125
+ 'agent-runtime-command-plan.yaml': {
126
+ ...toJSONSchema(agentRuntimeCommandPlanSchema),
127
+ $schema: JSON_SCHEMA_DRAFT,
128
+ title: 'CodeSDD Agent Runtime Command Plan',
129
+ description: 'Provider-normalized command plan for DeepAgents, Codex exec, and OpenCode run agent runtimes.',
130
+ },
131
+ 'agent-runtime-opencode-run-evidence.yaml': {
132
+ ...toJSONSchema(opencodeExecutionEvidenceSchema),
133
+ $schema: JSON_SCHEMA_DRAFT,
134
+ title: 'CodeSDD Agent Runtime OpenCode Run Evidence',
135
+ description: 'Machine-readable OpenCode run evidence with redacted excerpts, artifacts, validations, and finalize policy.',
136
+ },
137
+ };
138
+ }
139
+ function collectPolicyReasons(request) {
140
+ const reasons = [];
141
+ const mutating = request.mode === 'apply-sandbox' || request.mode === 'apply-approved';
142
+ if (mutating && request.approval_grants.length === 0) {
143
+ reasons.push(`Mode ${request.mode} requires explicit approval evidence before agent execution.`);
144
+ }
145
+ if (request.provider !== 'deepagents' && request.mode === 'apply-approved') {
146
+ reasons.push('Codex and OpenCode apply-approved execution is not enabled until plugin evidence ingestion is complete.');
147
+ }
148
+ if (request.provider === 'codex' && request.sandbox === 'danger-full-access') {
149
+ reasons.push('Codex danger-full-access sandbox is forbidden by the default CodeSDD agent runtime contract.');
150
+ }
151
+ return reasons;
152
+ }
153
+ function buildCommand(request) {
154
+ if (request.provider === 'deepagents') {
155
+ return {
156
+ executable: 'codesdd',
157
+ args: ['sdd', 'agent', 'run', request.feature_ref, '--provider', 'deepagents', '--mode', request.mode],
158
+ };
159
+ }
160
+ if (request.provider === 'codex') {
161
+ const args = ['exec', '--json', '--cd', request.cwd, '--sandbox', request.sandbox];
162
+ if (request.model)
163
+ args.push('--model', request.model);
164
+ if (request.output_schema)
165
+ args.push('--output-schema', request.output_schema);
166
+ if (request.output_last_message)
167
+ args.push('--output-last-message', request.output_last_message);
168
+ for (const file of request.files) {
169
+ args.push('--image', file);
170
+ }
171
+ args.push(request.instruction);
172
+ return {
173
+ executable: 'codex',
174
+ args,
175
+ };
176
+ }
177
+ const args = ['run', '--format', 'json', '--dir', request.cwd];
178
+ if (request.model)
179
+ args.push('--model', request.model);
180
+ if (request.agent)
181
+ args.push('--agent', request.agent);
182
+ if (request.session)
183
+ args.push('--session', request.session);
184
+ for (const file of request.files) {
185
+ args.push('--file', file);
186
+ }
187
+ args.push(request.instruction);
188
+ return {
189
+ executable: 'opencode',
190
+ args,
191
+ };
192
+ }
193
+ function evidenceContract(provider) {
194
+ if (provider === 'codex')
195
+ return 'agent-runtime-v2/codex-exec';
196
+ if (provider === 'opencode')
197
+ return 'agent-runtime-v2/opencode-run';
198
+ return 'agent-runtime-v2/deepagents';
199
+ }
200
+ //# sourceMappingURL=agent-runtime-contract.js.map
@@ -13,6 +13,8 @@ export interface SddCheckReport {
13
13
  backlog: number;
14
14
  techDebt: number;
15
15
  finalizeQueue: number;
16
+ finalizeQueuePending: number;
17
+ finalizeQueueDone: number;
16
18
  frontendEnabled: boolean;
17
19
  frontendGaps: number;
18
20
  frontendRoutes: number;
@@ -4,7 +4,7 @@ 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';
@@ -122,6 +122,13 @@ export function validateReferentialIntegrity(snapshot, errors, warnings, isStric
122
122
  }
123
123
  }
124
124
  }
125
+ if (snapshot.finalizeQueue?.history) {
126
+ for (const fq of snapshot.finalizeQueue.history) {
127
+ if (!backlogIds.has(fq.feature_id)) {
128
+ record(`Finalize history has entry for feature_id="${fq.feature_id}" blocked by an unknown feature.`);
129
+ }
130
+ }
131
+ }
125
132
  if (snapshot.unblockEvents?.events) {
126
133
  for (const ev of snapshot.unblockEvents.events) {
127
134
  if (!backlogIds.has(ev.feature_id)) {
@@ -172,6 +179,26 @@ export function validateSkillOperationalIntegrity(snapshot, errors) {
172
179
  }
173
180
  }
174
181
  }
182
+ const currentRoutes = new Map((snapshot.skillRouting?.routes || []).map((route) => [route.domain, route]));
183
+ for (const defaultRoute of buildDefaultSkillRoutingState().routes) {
184
+ const currentRoute = currentRoutes.get(defaultRoute.domain);
185
+ if (!currentRoute) {
186
+ errors.push(`Default skill route "${defaultRoute.domain}" is missing from skill-routing.yaml`);
187
+ continue;
188
+ }
189
+ const currentSkills = currentRoute.skills || [];
190
+ const currentBundles = currentRoute.bundles || [];
191
+ for (const skillId of defaultRoute.skills) {
192
+ if (!currentSkills.includes(skillId)) {
193
+ errors.push(`Default skill route "${defaultRoute.domain}" is missing skill "${skillId}"`);
194
+ }
195
+ }
196
+ for (const bundleId of defaultRoute.bundles) {
197
+ if (!currentBundles.includes(bundleId)) {
198
+ errors.push(`Default skill route "${defaultRoute.domain}" is missing bundle "${bundleId}"`);
199
+ }
200
+ }
201
+ }
175
202
  }
176
203
  export function computeGraphSummary(items) {
177
204
  const byId = new Map(items.map((item) => [item.id, item]));
@@ -541,6 +568,8 @@ export class SddCheckCommand {
541
568
  backlog: 0,
542
569
  techDebt: 0,
543
570
  finalizeQueue: 0,
571
+ finalizeQueuePending: 0,
572
+ finalizeQueueDone: 0,
544
573
  frontendEnabled: false,
545
574
  frontendGaps: 0,
546
575
  frontendRoutes: 0,
@@ -618,6 +647,8 @@ export class SddCheckCommand {
618
647
  backlog: 0,
619
648
  techDebt: 0,
620
649
  finalizeQueue: 0,
650
+ finalizeQueuePending: 0,
651
+ finalizeQueueDone: 0,
621
652
  frontendEnabled: config.frontend.enabled,
622
653
  frontendGaps: 0,
623
654
  frontendRoutes: 0,
@@ -658,6 +689,8 @@ export class SddCheckCommand {
658
689
  backlog: 0,
659
690
  techDebt: 0,
660
691
  finalizeQueue: 0,
692
+ finalizeQueuePending: 0,
693
+ finalizeQueueDone: 0,
661
694
  frontendEnabled: config.frontend.enabled,
662
695
  frontendGaps: 0,
663
696
  frontendRoutes: 0,
@@ -796,6 +829,9 @@ export class SddCheckCommand {
796
829
  featuresMissingFgapLink.length === 0;
797
830
  const qualityContractSync = featuresMissingQualityContract.length === 0 &&
798
831
  featuresMissingQualityArtifact.length === 0;
832
+ const finalizeQueuePending = snapshot.finalizeQueue.items.filter((item) => item.status === 'PENDING').length;
833
+ const finalizeQueueDone = snapshot.finalizeQueue.history.length +
834
+ snapshot.finalizeQueue.items.filter((item) => item.status === 'DONE').length;
799
835
  return {
800
836
  valid: errors.length === 0,
801
837
  errors,
@@ -804,7 +840,9 @@ export class SddCheckCommand {
804
840
  discovery: snapshot.discoveryIndex.records.length,
805
841
  backlog: snapshot.backlog.items.length,
806
842
  techDebt: snapshot.techDebt.items.length,
807
- finalizeQueue: snapshot.finalizeQueue.items.length,
843
+ finalizeQueue: finalizeQueuePending,
844
+ finalizeQueuePending,
845
+ finalizeQueueDone,
808
846
  frontendEnabled: config.frontend.enabled,
809
847
  frontendGaps: snapshot.frontendGaps?.items.length ?? 0,
810
848
  frontendRoutes: snapshot.frontendMap?.routes.length ?? 0,
@@ -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 interface RedisBoundaryConfig {
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: 'disabled' | 'requested-unavailable';
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.requested
216
- ? 'Redis adapter boundary is configured but no Redis client adapter is installed; filesystem-first defaults remain authoritative.'
217
- : 'Redis is optional and disabled; filesystem-first defaults are authoritative.',
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
@@ -1,2 +1,3 @@
1
1
  export * from './coordination-adapters.js';
2
+ export * from './redis-runtime.js';
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,3 @@
1
1
  export * from './coordination-adapters.js';
2
+ export * from './redis-runtime.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,131 @@
1
+ import type { GlobalConfig } from '../../global-config.js';
2
+ import type { CoordinationCacheAdapter, CoordinationEvent, CoordinationEventBusAdapter, CoordinationLockAdapter, CoordinationQueueAdapter } from './coordination-adapters.js';
3
+ import { type AcquireOptions } from '../state-lock.js';
4
+ export type RedisRuntimeStatus = 'disabled' | 'requested-unavailable' | 'ready' | 'degraded' | 'blocked';
5
+ export type RedisFallbackMode = 'filesystem' | 'none';
6
+ export interface RedisRuntimeConfig {
7
+ requested: boolean;
8
+ enabled: boolean;
9
+ namespace: string;
10
+ url?: string;
11
+ redactedUrl?: string;
12
+ tls: boolean;
13
+ connectTimeoutMs: number;
14
+ commandTimeoutMs: number;
15
+ maxRetries: number;
16
+ fallback: RedisFallbackMode;
17
+ cacheDefaultTtlMs: number;
18
+ lockTtlMs: number;
19
+ streamMaxLen: number;
20
+ validationErrors: string[];
21
+ warnings: string[];
22
+ }
23
+ export interface RedisOperationalReport {
24
+ requested: boolean;
25
+ namespace: string;
26
+ status: RedisRuntimeStatus;
27
+ fallback: RedisFallbackMode;
28
+ redacted_url?: string;
29
+ latency_ms?: number;
30
+ reason: string;
31
+ validation_errors: string[];
32
+ warnings: string[];
33
+ }
34
+ export interface RedisCommandClient {
35
+ isReady?: boolean;
36
+ isOpen?: boolean;
37
+ connect(): Promise<RedisCommandClient>;
38
+ ping(): Promise<string>;
39
+ sendCommand<T = unknown>(args: ReadonlyArray<string>): Promise<T>;
40
+ close?(): Promise<void>;
41
+ destroy?(): void;
42
+ on?(event: 'error', listener: (error: Error) => void): RedisCommandClient;
43
+ }
44
+ export type RedisClientCreator = (config: RedisRuntimeConfig) => RedisCommandClient;
45
+ export declare function redactRedisUrl(url: string | undefined): string | undefined;
46
+ export declare function sanitizeRedisError(error: unknown, config?: Pick<RedisRuntimeConfig, 'url'>): string;
47
+ export declare function hashRedisKey(input: string): string;
48
+ export declare function buildRedisKey(config: RedisRuntimeConfig, projectFingerprint: string, domain: string, key: string): string;
49
+ export declare function resolveRedisRuntimeConfig(env?: NodeJS.ProcessEnv, globalConfig?: Partial<GlobalConfig>): RedisRuntimeConfig;
50
+ export declare function resolveRedisBoundaryConfig(env?: NodeJS.ProcessEnv, globalConfig?: Partial<GlobalConfig>): RedisRuntimeConfig;
51
+ export declare class RedisClientFactory {
52
+ readonly config: RedisRuntimeConfig;
53
+ private readonly createClientImpl;
54
+ private client?;
55
+ private lastError?;
56
+ constructor(config: RedisRuntimeConfig, createClientImpl?: RedisClientCreator);
57
+ getClient(): Promise<RedisCommandClient>;
58
+ ping(): Promise<{
59
+ ok: boolean;
60
+ latencyMs?: number;
61
+ message: string;
62
+ }>;
63
+ command<T = unknown>(args: ReadonlyArray<string>): Promise<T>;
64
+ close(): Promise<void>;
65
+ getLastError(): string | undefined;
66
+ }
67
+ export declare function buildRedisOperationalReport(options?: {
68
+ env?: NodeJS.ProcessEnv;
69
+ globalConfig?: Partial<GlobalConfig>;
70
+ createClient?: RedisClientCreator;
71
+ }): Promise<RedisOperationalReport>;
72
+ export declare class RedisCoordinationCache implements CoordinationCacheAdapter {
73
+ private readonly redis;
74
+ private readonly fallback;
75
+ private readonly projectFingerprint;
76
+ private readonly domain;
77
+ constructor(redis: RedisClientFactory, fallback: CoordinationCacheAdapter, projectFingerprint: string, domain?: string);
78
+ get<T>(key: string): Promise<T | undefined>;
79
+ set<T>(key: string, value: T, options?: {
80
+ ttlMs?: number;
81
+ }): Promise<void>;
82
+ delete(key: string): Promise<void>;
83
+ clear(): Promise<void>;
84
+ }
85
+ export declare class RedisLockAdapter implements CoordinationLockAdapter {
86
+ private readonly redis;
87
+ private readonly fallback;
88
+ private readonly projectFingerprint;
89
+ constructor(redis: RedisClientFactory, fallback: CoordinationLockAdapter, projectFingerprint: string);
90
+ withLock<T>(scope: string, fn: () => Promise<T>, options?: AcquireOptions): Promise<T>;
91
+ extend(scope: string, ownerToken: string, ttlMs?: number): Promise<boolean>;
92
+ private release;
93
+ }
94
+ export declare class RedisQueueAdapter<T = unknown> implements CoordinationQueueAdapter<T> {
95
+ private readonly redis;
96
+ private readonly fallback;
97
+ private readonly projectFingerprint;
98
+ constructor(redis: RedisClientFactory, fallback: CoordinationQueueAdapter<T>, projectFingerprint: string);
99
+ enqueue(topic: string, item: T): Promise<void>;
100
+ dequeue(topic: string): Promise<T | undefined>;
101
+ size(topic: string): Promise<number>;
102
+ }
103
+ export declare class RedisEventBusAdapter implements CoordinationEventBusAdapter {
104
+ private readonly redis;
105
+ private readonly fallback;
106
+ private readonly projectFingerprint;
107
+ constructor(redis: RedisClientFactory, fallback: CoordinationEventBusAdapter, projectFingerprint: string);
108
+ publish(event: CoordinationEvent): Promise<void>;
109
+ drain(): Promise<CoordinationEvent[]>;
110
+ }
111
+ export declare class RedisWorkDeduper {
112
+ private readonly redis;
113
+ private readonly projectFingerprint;
114
+ constructor(redis: RedisClientFactory, projectFingerprint: string);
115
+ claim(key: string, ttlMs?: number): Promise<boolean>;
116
+ }
117
+ export declare function deleteKeysByPrefix(redis: RedisClientFactory, prefix: string): Promise<number>;
118
+ export declare function runRedisBenchmark(options: {
119
+ env?: NodeJS.ProcessEnv;
120
+ globalConfig?: Partial<GlobalConfig>;
121
+ createClient?: RedisClientCreator;
122
+ iterations?: number;
123
+ }): Promise<{
124
+ status: RedisRuntimeStatus;
125
+ namespace: string;
126
+ iterations: number;
127
+ p50_ms?: number;
128
+ p95_ms?: number;
129
+ reason: string;
130
+ }>;
131
+ //# sourceMappingURL=redis-runtime.d.ts.map