@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.
- package/.sdd/skills/curated/devtrack-api/SKILL.md +12 -5
- package/.sdd/skills/curated/devtrack-api/agents/claude-code.yaml +8 -0
- package/.sdd/skills/curated/devtrack-api/agents/codex.yaml +8 -0
- package/.sdd/skills/curated/devtrack-api/agents/cursor.yaml +8 -0
- package/.sdd/skills/curated/devtrack-api/agents/gemini.yaml +8 -0
- package/.sdd/skills/curated/devtrack-api/agents/kimi.yaml +8 -0
- package/.sdd/skills/curated/devtrack-api/agents/openai.yaml +4 -2
- package/.sdd/skills/curated/devtrack-api/agents/opencode.yaml +10 -0
- package/.sdd/skills/curated/devtrack-api/references/application-presentation.md +2 -2
- package/.sdd/skills/curated/devtrack-api/references/contract-pack.yaml +55 -0
- package/.sdd/skills/curated/devtrack-api/references/domain-modeling.md +13 -13
- package/.sdd/skills/curated/devtrack-api/references/foundation-layout.md +2 -3
- package/.sdd/skills/curated/devtrack-api/references/implementation-checklist.md +1 -1
- package/.sdd/skills/curated/devtrack-api/references/portable-agent-contract.md +41 -0
- package/.sdd/skills/curated/devtrack-api/references/typeorm-infrastructure.md +7 -9
- package/README.md +159 -5
- package/dist/applications/sdd/index.d.ts +16 -0
- package/dist/applications/sdd/index.js +16 -0
- package/dist/commands/config.js +171 -10
- package/dist/commands/sdd/execution.js +345 -15
- 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 +38 -3
- package/dist/core/cli/command-matrix.js +9 -0
- package/dist/core/cli-command-quality.js +9 -0
- package/dist/core/completions/command-registry.js +45 -0
- package/dist/core/config-schema.d.ts +18 -1
- package/dist/core/config-schema.js +48 -5
- package/dist/core/global-config.d.ts +16 -0
- package/dist/core/sdd/agent-binding.d.ts +10 -10
- 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/check.d.ts +2 -0
- package/dist/core/sdd/check.js +40 -2
- 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 +98 -4
- package/dist/core/sdd/deepagent-contracts.js +62 -0
- package/dist/core/sdd/default-bootstrap-files.d.ts +2 -2
- package/dist/core/sdd/default-bootstrap-files.js +14 -8
- package/dist/core/sdd/default-skills.js +108 -4
- package/dist/core/sdd/devtrack-api-appliance.d.ts +8 -1
- package/dist/core/sdd/devtrack-api-appliance.js +46 -23
- package/dist/core/sdd/docs-sync.js +21 -15
- 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/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/intent-guard.d.ts +22 -0
- package/dist/core/sdd/intent-guard.js +67 -0
- package/dist/core/sdd/json-schema.js +9 -1
- package/dist/core/sdd/legacy-operations.js +76 -1
- 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 +119 -0
- package/dist/core/sdd/package-structure-gate.js +3 -8
- package/dist/core/sdd/parallel-feat-automation.d.ts +181 -3
- 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-registry.d.ts +141 -5
- 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 +84 -11
- package/dist/core/sdd/release-readiness.d.ts +19 -0
- package/dist/core/sdd/release-readiness.js +472 -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/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/finalize.service.d.ts +25 -0
- package/dist/core/sdd/services/finalize.service.js +178 -16
- package/dist/core/sdd/services/frontend-impact.service.d.ts +1 -1
- 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 +251 -29
- 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 +27 -12
- package/dist/core/sdd/types.js +4 -0
- package/dist/core/sdd/views.js +17 -0
- package/dist/core/sdd/workspace-schemas.d.ts +387 -7
- package/dist/core/sdd/workspace-schemas.js +196 -64
- 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 +9 -6
- package/schemas/sdd/2-plan.schema.json +207 -2
- package/schemas/sdd/5-quality.schema.json +281 -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/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 +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
|
package/dist/core/sdd/check.d.ts
CHANGED
package/dist/core/sdd/check.js
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
|
@@ -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
|