@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
|
@@ -10,12 +10,78 @@ import { normalizeArchivedFeatureActiveReferences, validatePostActiveFeaturePlac
|
|
|
10
10
|
import { loadStateSnapshot, nowIso, saveStateTransaction } from "../state.js";
|
|
11
11
|
import { syncSddGuideDocs } from "../docs-sync.js";
|
|
12
12
|
import { mergeArchitectureNode, mergeFrontendDecisionRecord, mergeRepoMapRecord, mergeServiceRecord, mergeTechStackRecord, stableUniqueStrings, upsertByKey } from "../merge-catalog.js";
|
|
13
|
-
import { getRuntime, persistAndRender, relProjectPath, coreDocRef, activeDocNamesForLayout, activeDocCandidateNames, unresolvedDependencies, updateDependencyMetadata, gitChangedFiles, detectFrontendImpactEvidence, maybeCreateAutomaticFrontendGap, buildFinalizeQueue, evaluateFeatureQuality, buildAdrMarkdown, applyLoggedTransition, gateSatisfied, pathExists, RADAR_TO_DISCOVERY_STATUS } from "../legacy-operations.js";
|
|
13
|
+
import { getRuntime, persistAndRender, relProjectPath, coreDocRef, activeDocNamesForLayout, activeDocCandidateNames, unresolvedDependencies, updateDependencyMetadata, gitChangedFiles, detectFrontendImpactEvidence, maybeCreateAutomaticFrontendGap, buildFinalizeQueue, evaluateFeatureQuality, buildAdrMarkdown, applyLoggedTransition, gateSatisfied, pathExists, computeReadyFeatures, extractFeatureNumber, buildDependentsMap, scoreReadyItem, RADAR_TO_DISCOVERY_STATUS } from "../legacy-operations.js";
|
|
14
14
|
import { SddWriteTransaction } from "../write-manifest.js";
|
|
15
15
|
import { withStateLock } from "../state-lock.js";
|
|
16
16
|
import { activeWorkspaceDeclaresMandatoryAdrImpact } from "../adr-policy.js";
|
|
17
17
|
import { normalizeFeatRef } from "../entity-reference.js";
|
|
18
18
|
import { parseWorkspaceYamlDocument, stringifyWorkspaceYamlDocument, } from "../workspace-schemas.js";
|
|
19
|
+
function recordFinalizeHistory(history, item) {
|
|
20
|
+
const index = history.findIndex((entry) => entry.feature_id === item.feature_id);
|
|
21
|
+
if (index >= 0) {
|
|
22
|
+
history[index] = item;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
history.push(item);
|
|
26
|
+
history.sort((a, b) => a.feature_id.localeCompare(b.feature_id));
|
|
27
|
+
}
|
|
28
|
+
function moveCompletedFinalizeItemsToHistory(state) {
|
|
29
|
+
const activeItems = [];
|
|
30
|
+
for (const item of state.items) {
|
|
31
|
+
if (item.status === 'DONE') {
|
|
32
|
+
recordFinalizeHistory(state.history, {
|
|
33
|
+
feature_id: item.feature_id,
|
|
34
|
+
status: 'DONE',
|
|
35
|
+
summary: item.summary,
|
|
36
|
+
created_at: item.created_at,
|
|
37
|
+
completed_at: item.completed_at || item.created_at || '',
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
activeItems.push(item);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
state.items = activeItems;
|
|
45
|
+
}
|
|
46
|
+
export function reconcileAdrMarkdownForFinalize(existingAdr, now) {
|
|
47
|
+
return existingAdr
|
|
48
|
+
.replace(/^status:\s*IN_PROGRESS\b/m, 'status: DONE')
|
|
49
|
+
.replace(/^status:\s*DRAFT\b/m, 'status: DONE')
|
|
50
|
+
.replace(/^decision_status:\s*PROPOSED\b/m, 'decision_status: ACCEPTED')
|
|
51
|
+
.replace(/^updated_at:\s*["']?[\dTZ:.-]+["']?\s*$/m, `updated_at: ${now}`);
|
|
52
|
+
}
|
|
53
|
+
function buildPostFinalizeReplan(items, finalized, unlocked, generatedAt) {
|
|
54
|
+
const rank = 'impact';
|
|
55
|
+
const computed = computeReadyFeatures(items, { rank });
|
|
56
|
+
const dependentsMap = buildDependentsMap(items);
|
|
57
|
+
const ready = computed.ready
|
|
58
|
+
.map((item) => {
|
|
59
|
+
const scored = scoreReadyItem(item, items, dependentsMap, rank);
|
|
60
|
+
return {
|
|
61
|
+
id: item.id,
|
|
62
|
+
title: item.title,
|
|
63
|
+
recommended_skills: item.recommended_skills.slice(0, 3),
|
|
64
|
+
score: scored.score,
|
|
65
|
+
reasons: scored.reasons,
|
|
66
|
+
};
|
|
67
|
+
})
|
|
68
|
+
.sort((left, right) => {
|
|
69
|
+
if (right.score !== left.score)
|
|
70
|
+
return right.score - left.score;
|
|
71
|
+
return extractFeatureNumber(left.id) - extractFeatureNumber(right.id);
|
|
72
|
+
})
|
|
73
|
+
.slice(0, 10);
|
|
74
|
+
return {
|
|
75
|
+
rank,
|
|
76
|
+
generated_at: generatedAt,
|
|
77
|
+
finalized,
|
|
78
|
+
unlocked,
|
|
79
|
+
ready,
|
|
80
|
+
waves: computed.waves,
|
|
81
|
+
blocked_count: computed.blocked.length,
|
|
82
|
+
conflicts_count: computed.conflicts.length,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
19
85
|
export class FinalizeService {
|
|
20
86
|
stores;
|
|
21
87
|
constructor(stores) {
|
|
@@ -25,6 +91,7 @@ export class FinalizeService {
|
|
|
25
91
|
const { config, paths } = await getRuntime(projectRoot);
|
|
26
92
|
return withStateLock(paths.stateDir, async () => {
|
|
27
93
|
const snapshot = await loadStateSnapshot(paths, config);
|
|
94
|
+
moveCompletedFinalizeItemsToHistory(snapshot.finalizeQueue);
|
|
28
95
|
snapshot.finalizeQueue.items = await buildFinalizeQueue(paths, snapshot.backlog.items, snapshot.finalizeQueue.items);
|
|
29
96
|
const pending = snapshot.finalizeQueue.items.filter((item) => item.status === 'PENDING');
|
|
30
97
|
const inferredRef = options?.allReady
|
|
@@ -47,6 +114,7 @@ export class FinalizeService {
|
|
|
47
114
|
finalized: [],
|
|
48
115
|
unblocked: [],
|
|
49
116
|
pending: pending.length,
|
|
117
|
+
post_finalize_replan: buildPostFinalizeReplan(snapshot.backlog.items, [], [], nowIso()),
|
|
50
118
|
updated_core_docs: [],
|
|
51
119
|
updated_readme: false,
|
|
52
120
|
updated_agent_guide: false,
|
|
@@ -275,19 +343,14 @@ export class FinalizeService {
|
|
|
275
343
|
continue;
|
|
276
344
|
}
|
|
277
345
|
const queue = snapshot.finalizeQueue.items.find((item) => item.feature_id === featureId);
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
summary: `Finalizado manualmente: ${featureId}`,
|
|
287
|
-
created_at: now,
|
|
288
|
-
completed_at: now,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
346
|
+
snapshot.finalizeQueue.items = snapshot.finalizeQueue.items.filter((item) => item.feature_id !== featureId);
|
|
347
|
+
recordFinalizeHistory(snapshot.finalizeQueue.history, {
|
|
348
|
+
feature_id: featureId,
|
|
349
|
+
status: 'DONE',
|
|
350
|
+
summary: queue?.summary || `Finalizado manualmente: ${featureId}`,
|
|
351
|
+
created_at: queue?.created_at || now,
|
|
352
|
+
completed_at: now,
|
|
353
|
+
});
|
|
291
354
|
if ((feature.origin_type === 'radar' || feature.origin_type === 'epic') && feature.origin_ref) {
|
|
292
355
|
const siblings = snapshot.backlog.items.filter((item) => (item.origin_type === 'radar' || item.origin_type === 'epic') && item.origin_ref === feature.origin_ref);
|
|
293
356
|
if (siblings.every((item) => item.status === 'DONE' || item.status === 'ARCHIVED')) {
|
|
@@ -395,7 +458,13 @@ export class FinalizeService {
|
|
|
395
458
|
if (!options?.noAdr && !feature.requires_adr) {
|
|
396
459
|
const adrPath = path.join(paths.coreDir, 'adrs', `ADR-${feature.id}.md`);
|
|
397
460
|
const tx = new SddWriteTransaction();
|
|
398
|
-
|
|
461
|
+
try {
|
|
462
|
+
const existingAdr = await fs.readFile(adrPath, 'utf-8');
|
|
463
|
+
tx.writeFile(adrPath, reconcileAdrMarkdownForFinalize(existingAdr, now));
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
tx.writeFile(adrPath, buildAdrMarkdown(feature, unlockedByFeature, now));
|
|
467
|
+
}
|
|
399
468
|
await tx.commit(paths.projectRoot, paths.memoryRoot, 'finalize.service (ADR)');
|
|
400
469
|
}
|
|
401
470
|
await fs.mkdir(paths.archivedDir, { recursive: true });
|
|
@@ -421,6 +490,7 @@ export class FinalizeService {
|
|
|
421
490
|
});
|
|
422
491
|
}
|
|
423
492
|
updateDependencyMetadata(snapshot.backlog.items);
|
|
493
|
+
const postFinalizeReplan = buildPostFinalizeReplan(snapshot.backlog.items, finalized, Array.from(unblocked).sort(), now);
|
|
424
494
|
const stateUpdates = {
|
|
425
495
|
discoveryIndex: snapshot.discoveryIndex,
|
|
426
496
|
backlog: snapshot.backlog,
|
|
@@ -454,6 +524,7 @@ export class FinalizeService {
|
|
|
454
524
|
finalized,
|
|
455
525
|
unblocked: Array.from(unblocked).sort(),
|
|
456
526
|
pending: remaining,
|
|
527
|
+
post_finalize_replan: postFinalizeReplan,
|
|
457
528
|
updated_core_docs: Array.from(updatedCoreDocs).sort(),
|
|
458
529
|
updated_readme: syncResult.updatedReadme,
|
|
459
530
|
updated_agent_guide: syncResult.updatedAgentGuide || syncResult.updatedRootAgents,
|
|
@@ -643,6 +714,7 @@ export async function evaluateWorkspaceQualityFeedback(paths, config, feature) {
|
|
|
643
714
|
}
|
|
644
715
|
const missingTargets = missingCoverageTargets(document);
|
|
645
716
|
const missingSkills = missingSkillEvidence(document);
|
|
717
|
+
const criticalProvenanceReasons = missingCriticalEvidenceProvenance(document);
|
|
646
718
|
const matrixReasons = evaluateQualityWitnessMatrix(document);
|
|
647
719
|
const reasons = [];
|
|
648
720
|
const maxRounds = document.remediation_policy.max_rounds;
|
|
@@ -670,6 +742,9 @@ export async function evaluateWorkspaceQualityFeedback(paths, config, feature) {
|
|
|
670
742
|
if (missingSkills.length > 0) {
|
|
671
743
|
reasons.push(`required skill evidence missing for ${missingSkills.join(', ')}`);
|
|
672
744
|
}
|
|
745
|
+
if (criticalProvenanceReasons.length > 0) {
|
|
746
|
+
reasons.push(...criticalProvenanceReasons);
|
|
747
|
+
}
|
|
673
748
|
if (matrixReasons.length > 0) {
|
|
674
749
|
reasons.push(`quality-witness matrix blocked finalize: ${matrixReasons.join(' | ')}`);
|
|
675
750
|
}
|
|
@@ -825,19 +900,65 @@ export function computeIntegrityAxis(document) {
|
|
|
825
900
|
integritySignalScore(security.sensitive_data_exposure_review),
|
|
826
901
|
integritySignalScore(security.incident_response_review),
|
|
827
902
|
].reduce((acc, value) => acc + value, 0) / 3);
|
|
828
|
-
const
|
|
903
|
+
const runtimeQuality = computeRuntimeQualityAxis(document);
|
|
904
|
+
const raw = round2(skillScore * 0.55 + securityScore * 0.25 + runtimeQuality.raw_score * 0.2);
|
|
829
905
|
return {
|
|
830
906
|
raw_score: raw,
|
|
831
|
-
rationale: `skill_evidence=${skillScore.toFixed(2)} security_integrity=${securityScore.toFixed(2)}`,
|
|
907
|
+
rationale: `skill_evidence=${skillScore.toFixed(2)} security_integrity=${securityScore.toFixed(2)} runtime_quality=${runtimeQuality.raw_score.toFixed(2)} (${runtimeQuality.rationale})`,
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
export function computeRuntimeQualityAxis(document) {
|
|
911
|
+
const gates = document.runtime_quality_gates;
|
|
912
|
+
const performance = gates?.performance ?? [];
|
|
913
|
+
const flakiness = gates?.flakiness ?? [];
|
|
914
|
+
const entries = [
|
|
915
|
+
...performance.map((entry) => ({ kind: 'performance', gate: entry.gate, evidence: entry.evidence_ref, score: runtimePerformanceScore(entry) })),
|
|
916
|
+
...flakiness.map((entry) => ({ kind: 'flakiness', gate: entry.gate, evidence: entry.evidence_ref, score: runtimeFlakinessScore(entry) })),
|
|
917
|
+
];
|
|
918
|
+
if (entries.length === 0) {
|
|
919
|
+
return {
|
|
920
|
+
raw_score: 100,
|
|
921
|
+
rationale: 'runtime quality telemetry not provided; neutral score applied',
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
const scores = entries.map((entry) => {
|
|
925
|
+
if (entry.gate === 'fail')
|
|
926
|
+
return Math.min(entry.score, 70);
|
|
927
|
+
if (entry.gate === 'warn')
|
|
928
|
+
return Math.min(entry.score, 85);
|
|
929
|
+
return entry.score;
|
|
930
|
+
});
|
|
931
|
+
const worst = Math.min(...scores);
|
|
932
|
+
const failed = entries.filter((entry) => entry.gate === 'fail').map((entry) => `${entry.kind}:${entry.evidence ?? 'runtime evidence'}`);
|
|
933
|
+
const warned = entries.filter((entry) => entry.gate === 'warn').map((entry) => `${entry.kind}:${entry.evidence ?? 'runtime evidence'}`);
|
|
934
|
+
const notes = [...failed.map((value) => `${value} failed`), ...warned.map((value) => `${value} warned`)];
|
|
935
|
+
return {
|
|
936
|
+
raw_score: worst,
|
|
937
|
+
rationale: `runtime quality gate scored ${worst.toFixed(2)}${notes.length > 0 ? `; ${notes.join('; ')}` : ''}`,
|
|
832
938
|
};
|
|
833
939
|
}
|
|
940
|
+
function runtimePerformanceScore(entry) {
|
|
941
|
+
if (entry.actual !== undefined && entry.threshold !== undefined && entry.threshold > 0) {
|
|
942
|
+
return clampPercent((entry.threshold / Math.max(entry.actual, 1)) * 100);
|
|
943
|
+
}
|
|
944
|
+
return entry.gate === 'pass' ? 100 : entry.gate === 'warn' ? 85 : 70;
|
|
945
|
+
}
|
|
946
|
+
function runtimeFlakinessScore(entry) {
|
|
947
|
+
let failureRate = entry.failure_rate_percent;
|
|
948
|
+
if (failureRate === undefined && entry.attempts !== undefined && entry.failures !== undefined && entry.attempts > 0) {
|
|
949
|
+
failureRate = clampPercent((entry.failures / entry.attempts) * 100);
|
|
950
|
+
}
|
|
951
|
+
if (failureRate === undefined) {
|
|
952
|
+
return entry.gate === 'pass' ? 100 : entry.gate === 'warn' ? 85 : 70;
|
|
953
|
+
}
|
|
954
|
+
return clampPercent(100 - failureRate);
|
|
955
|
+
}
|
|
834
956
|
const LEGACY_NAMING_PATTERNS = [
|
|
835
957
|
/\bcoachsdd\b/u,
|
|
836
958
|
/\bcoach\s+sdd\b/u,
|
|
837
959
|
/\bcoach-sdd\b/u,
|
|
838
960
|
/\bcoach_sdd\b/u,
|
|
839
961
|
/\bsdd\s+coach\b/u,
|
|
840
|
-
/\.codesdd\b/u,
|
|
841
962
|
];
|
|
842
963
|
export function computeNamingAxis(feature, document) {
|
|
843
964
|
let score = 100;
|
|
@@ -861,6 +982,10 @@ export function computeNamingAxis(feature, document) {
|
|
|
861
982
|
};
|
|
862
983
|
}
|
|
863
984
|
export function computeTokenAxis(document) {
|
|
985
|
+
const structuredTelemetry = document.token_budget_gates?.telemetry ?? [];
|
|
986
|
+
if (structuredTelemetry.length > 0) {
|
|
987
|
+
return computeStructuredTokenAxis(document);
|
|
988
|
+
}
|
|
864
989
|
const tokenEntries = document.evidence_log.filter((entry) => {
|
|
865
990
|
const label = `${entry.kind} ${entry.result}`.toLowerCase();
|
|
866
991
|
return label.includes('token');
|
|
@@ -893,6 +1018,59 @@ export function computeTokenAxis(document) {
|
|
|
893
1018
|
rationale: 'token evidence found but no numeric percentage; conservative score applied',
|
|
894
1019
|
};
|
|
895
1020
|
}
|
|
1021
|
+
export function computeStructuredTokenAxis(document) {
|
|
1022
|
+
const gates = document.token_budget_gates;
|
|
1023
|
+
const telemetry = gates?.telemetry ?? [];
|
|
1024
|
+
const requireNumeric = gates?.require_numeric_efficiency ?? true;
|
|
1025
|
+
const failBelow = gates?.fail_below_percent ?? 95;
|
|
1026
|
+
const scores = [];
|
|
1027
|
+
const reasons = [];
|
|
1028
|
+
let hardFailure = false;
|
|
1029
|
+
for (const entry of telemetry) {
|
|
1030
|
+
let score = entry.efficiency_percent;
|
|
1031
|
+
if (score === undefined && entry.budget_chars !== undefined && entry.actual_chars !== undefined) {
|
|
1032
|
+
score = entry.actual_chars === 0 ? 100 : clampPercent((entry.budget_chars / entry.actual_chars) * 100);
|
|
1033
|
+
}
|
|
1034
|
+
if (score === undefined && entry.actual_chars !== undefined && gates?.max_context_chars !== undefined) {
|
|
1035
|
+
score = entry.actual_chars === 0 ? 100 : clampPercent((gates.max_context_chars / entry.actual_chars) * 100);
|
|
1036
|
+
}
|
|
1037
|
+
if (score === undefined) {
|
|
1038
|
+
if (requireNumeric) {
|
|
1039
|
+
scores.push(70);
|
|
1040
|
+
reasons.push(`${entry.evidence_ref ?? 'token telemetry'} lacks numeric efficiency`);
|
|
1041
|
+
}
|
|
1042
|
+
continue;
|
|
1043
|
+
}
|
|
1044
|
+
const normalized = clampPercent(score);
|
|
1045
|
+
scores.push(normalized);
|
|
1046
|
+
if (entry.gate === 'fail' || normalized < failBelow) {
|
|
1047
|
+
hardFailure = true;
|
|
1048
|
+
reasons.push(`${entry.evidence_ref ?? 'token telemetry'} below ${failBelow}%`);
|
|
1049
|
+
}
|
|
1050
|
+
else if (entry.gate === 'warn') {
|
|
1051
|
+
reasons.push(`${entry.evidence_ref ?? 'token telemetry'} warned at ${normalized.toFixed(2)}%`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (scores.length === 0) {
|
|
1055
|
+
return {
|
|
1056
|
+
raw_score: requireNumeric ? 70 : 100,
|
|
1057
|
+
rationale: requireNumeric
|
|
1058
|
+
? 'token budget gate requires numeric efficiency but no numeric telemetry was provided'
|
|
1059
|
+
: 'token budget telemetry provided without numeric scoring requirement',
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
const worst = Math.min(...scores);
|
|
1063
|
+
if (hardFailure) {
|
|
1064
|
+
return {
|
|
1065
|
+
raw_score: Math.min(worst, 70),
|
|
1066
|
+
rationale: `token budget gate failed: ${reasons.join('; ')}`,
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
raw_score: worst,
|
|
1071
|
+
rationale: `token budget gate satisfied (${worst.toFixed(2)}% worst-case efficiency)${reasons.length > 0 ? `; ${reasons.join('; ')}` : ''}`,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
896
1074
|
export function extractCoveragePerformance(document, kind) {
|
|
897
1075
|
const target = kind === 'unit' ? document.coverage_targets.unit : document.coverage_targets.integration;
|
|
898
1076
|
const relevant = document.evidence_log.filter((entry) => {
|
|
@@ -1003,4 +1181,34 @@ export function missingSkillEvidence(document) {
|
|
|
1003
1181
|
.filter(Boolean));
|
|
1004
1182
|
return required.filter((skillId) => !provided.has(skillId.toLowerCase()));
|
|
1005
1183
|
}
|
|
1184
|
+
export function missingCriticalEvidenceProvenance(document) {
|
|
1185
|
+
const reasons = [];
|
|
1186
|
+
document.evidence_log.forEach((entry, index) => {
|
|
1187
|
+
if (entry.critical !== true) {
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const provenance = entry.provenance;
|
|
1191
|
+
if (!provenance) {
|
|
1192
|
+
reasons.push(`critical evidence_log[${index}] (${entry.kind}) missing provenance (command, exit_code, timestamp_iso, commit_sha, artifact_path, artifact_hash)`);
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
const missingFields = [];
|
|
1196
|
+
if (!provenance.command?.trim())
|
|
1197
|
+
missingFields.push('command');
|
|
1198
|
+
if (!Number.isInteger(provenance.exit_code))
|
|
1199
|
+
missingFields.push('exit_code');
|
|
1200
|
+
if (!provenance.timestamp_iso?.trim())
|
|
1201
|
+
missingFields.push('timestamp_iso');
|
|
1202
|
+
if (!provenance.commit_sha?.trim())
|
|
1203
|
+
missingFields.push('commit_sha');
|
|
1204
|
+
if (!provenance.artifact_path?.trim())
|
|
1205
|
+
missingFields.push('artifact_path');
|
|
1206
|
+
if (!provenance.artifact_hash?.trim())
|
|
1207
|
+
missingFields.push('artifact_hash');
|
|
1208
|
+
if (missingFields.length > 0) {
|
|
1209
|
+
reasons.push(`critical evidence_log[${index}] (${entry.kind}) missing provenance fields: ${missingFields.join(', ')}`);
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
return reasons;
|
|
1213
|
+
}
|
|
1006
1214
|
//# sourceMappingURL=finalize.service.js.map
|
|
@@ -10,7 +10,7 @@ export declare class FrontendImpactService {
|
|
|
10
10
|
render?: boolean;
|
|
11
11
|
}): Promise<{
|
|
12
12
|
feature_id: string;
|
|
13
|
-
frontend_impact_status: "
|
|
13
|
+
frontend_impact_status: "none" | "unknown" | "required";
|
|
14
14
|
frontend_impact_reason: string;
|
|
15
15
|
frontend_impact_declared_at: string;
|
|
16
16
|
frontend_surface_tokens: string[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type HistoricalQualityRegressionMode = 'recent-window' | 'full-history';
|
|
2
|
+
export type HistoricalQualityRegressionIssueCode = 'ACCEPTANCE_NOT_MET' | 'OPEN_RISK' | 'PLACEHOLDER_EVIDENCE' | 'MISSING_ADR' | 'FRONTEND_IMPACT_UNKNOWN' | 'MISSING_DONE_AT' | 'MISSING_ARCHIVED_AT' | 'STATE_CORE_DIVERGENCE';
|
|
3
|
+
export interface HistoricalQualityRegressionIssue {
|
|
4
|
+
feature_id: string;
|
|
5
|
+
code: HistoricalQualityRegressionIssueCode;
|
|
6
|
+
message: string;
|
|
7
|
+
quality_path?: string;
|
|
8
|
+
backlog_path?: string;
|
|
9
|
+
adr_path?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface HistoricalQualityRegressionReport {
|
|
12
|
+
generated_at: string;
|
|
13
|
+
mode: HistoricalQualityRegressionMode;
|
|
14
|
+
recent_window_size: number | null;
|
|
15
|
+
scanned: number;
|
|
16
|
+
considered: number;
|
|
17
|
+
analyzed: number;
|
|
18
|
+
grandfathered: number;
|
|
19
|
+
issue_count: number;
|
|
20
|
+
grandfathering: {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
max_feat_id: number;
|
|
23
|
+
policy: string;
|
|
24
|
+
};
|
|
25
|
+
issues: HistoricalQualityRegressionIssue[];
|
|
26
|
+
}
|
|
27
|
+
export interface HistoricalQualityRegressionOptions {
|
|
28
|
+
mode?: HistoricalQualityRegressionMode;
|
|
29
|
+
recentWindowSize?: number;
|
|
30
|
+
grandfatherMaxFeat?: number;
|
|
31
|
+
}
|
|
32
|
+
export declare class HistoricalQualityRegressionService {
|
|
33
|
+
execute(projectRoot: string, options?: HistoricalQualityRegressionOptions): Promise<HistoricalQualityRegressionReport>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=historical-quality-regression.service.d.ts.map
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { existsSync, promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import { parseWorkspaceYamlDocument } from '../workspace-schemas.js';
|
|
5
|
+
const PLACEHOLDER_PATTERNS = [
|
|
6
|
+
/\(fill in/i,
|
|
7
|
+
/\(placeholder/i,
|
|
8
|
+
/\btodo\b/i,
|
|
9
|
+
/\btbd\b/i,
|
|
10
|
+
/\bpending\b/i,
|
|
11
|
+
/\bmust implement\b/i,
|
|
12
|
+
/\bto be executed\b/i,
|
|
13
|
+
/\bplanned\b/i,
|
|
14
|
+
/\bwill be\b/i,
|
|
15
|
+
/\bto do\b/i,
|
|
16
|
+
];
|
|
17
|
+
const DEFAULT_RECENT_WINDOW_SIZE = 25;
|
|
18
|
+
const DEFAULT_GRANDFATHER_MAX_FEAT = 451;
|
|
19
|
+
function featNumericId(featureId) {
|
|
20
|
+
const match = featureId.match(/^FEAT-(\d{4,})$/);
|
|
21
|
+
if (!match)
|
|
22
|
+
return null;
|
|
23
|
+
return Number.parseInt(match[1] || '', 10);
|
|
24
|
+
}
|
|
25
|
+
function hasPlaceholderSignal(text) {
|
|
26
|
+
return PLACEHOLDER_PATTERNS.some((pattern) => pattern.test(text));
|
|
27
|
+
}
|
|
28
|
+
function hasPlaceholderEvidence(document) {
|
|
29
|
+
const evidenceTexts = [
|
|
30
|
+
...document.evidence_log.flatMap((entry) => [entry.kind, entry.result, entry.artifact ?? '']),
|
|
31
|
+
...document.acceptance_matrix.flatMap((entry) => [entry.criterion, entry.evidence]),
|
|
32
|
+
];
|
|
33
|
+
return evidenceTexts.some((text) => text && hasPlaceholderSignal(text));
|
|
34
|
+
}
|
|
35
|
+
function sortFeaturesByRecency(featureIds) {
|
|
36
|
+
return [...featureIds].sort((left, right) => {
|
|
37
|
+
const leftNum = featNumericId(left);
|
|
38
|
+
const rightNum = featNumericId(right);
|
|
39
|
+
if (leftNum !== null && rightNum !== null && leftNum !== rightNum) {
|
|
40
|
+
return rightNum - leftNum;
|
|
41
|
+
}
|
|
42
|
+
if (leftNum !== null && rightNum === null)
|
|
43
|
+
return -1;
|
|
44
|
+
if (leftNum === null && rightNum !== null)
|
|
45
|
+
return 1;
|
|
46
|
+
return right.localeCompare(left);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function loadBacklogEvidence(backlogPath) {
|
|
50
|
+
const map = new Map();
|
|
51
|
+
if (!existsSync(backlogPath))
|
|
52
|
+
return map;
|
|
53
|
+
const raw = await fs.readFile(backlogPath, 'utf-8').catch(() => '');
|
|
54
|
+
if (!raw.trim())
|
|
55
|
+
return map;
|
|
56
|
+
const parsed = parseYaml(raw);
|
|
57
|
+
for (const item of parsed.items ?? []) {
|
|
58
|
+
const id = typeof item.id === 'string' ? item.id : '';
|
|
59
|
+
if (!id.startsWith('FEAT-'))
|
|
60
|
+
continue;
|
|
61
|
+
map.set(id, {
|
|
62
|
+
id,
|
|
63
|
+
status: typeof item.status === 'string' ? item.status : undefined,
|
|
64
|
+
frontend_impact_status: typeof item.frontend_impact_status === 'string' ? item.frontend_impact_status : undefined,
|
|
65
|
+
done_at: typeof item.done_at === 'string' ? item.done_at : undefined,
|
|
66
|
+
archived_at: typeof item.archived_at === 'string' ? item.archived_at : undefined,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
71
|
+
export class HistoricalQualityRegressionService {
|
|
72
|
+
async execute(projectRoot, options = {}) {
|
|
73
|
+
const mode = options.mode ?? 'recent-window';
|
|
74
|
+
const recentWindowSize = options.recentWindowSize ?? DEFAULT_RECENT_WINDOW_SIZE;
|
|
75
|
+
const grandfatherMaxFeat = options.grandfatherMaxFeat ?? DEFAULT_GRANDFATHER_MAX_FEAT;
|
|
76
|
+
const archivedDir = path.join(projectRoot, '.sdd', 'archived');
|
|
77
|
+
const backlogPath = path.join(projectRoot, '.sdd', 'state', 'backlog.yaml');
|
|
78
|
+
const adrsDir = path.join(projectRoot, '.sdd', 'core', 'adrs');
|
|
79
|
+
const backlogByFeature = await loadBacklogEvidence(backlogPath);
|
|
80
|
+
if (!existsSync(archivedDir)) {
|
|
81
|
+
return {
|
|
82
|
+
generated_at: new Date().toISOString(),
|
|
83
|
+
mode,
|
|
84
|
+
recent_window_size: mode === 'recent-window' ? recentWindowSize : null,
|
|
85
|
+
scanned: 0,
|
|
86
|
+
considered: 0,
|
|
87
|
+
analyzed: 0,
|
|
88
|
+
grandfathered: 0,
|
|
89
|
+
issue_count: 0,
|
|
90
|
+
grandfathering: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
max_feat_id: grandfatherMaxFeat,
|
|
93
|
+
policy: `Skip blocking regression checks for archived FEAT ids <= FEAT-${String(grandfatherMaxFeat).padStart(4, '0')}.`,
|
|
94
|
+
},
|
|
95
|
+
issues: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
const entries = await fs.readdir(archivedDir, { withFileTypes: true });
|
|
99
|
+
const scannedFeatures = entries
|
|
100
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith('FEAT-'))
|
|
101
|
+
.map((entry) => entry.name);
|
|
102
|
+
const orderedFeatures = sortFeaturesByRecency(scannedFeatures);
|
|
103
|
+
const consideredFeatures = mode === 'recent-window' ? orderedFeatures.slice(0, Math.max(1, recentWindowSize)) : orderedFeatures;
|
|
104
|
+
let analyzed = 0;
|
|
105
|
+
let grandfathered = 0;
|
|
106
|
+
const issues = [];
|
|
107
|
+
for (const featureId of consideredFeatures) {
|
|
108
|
+
const numericId = featNumericId(featureId);
|
|
109
|
+
if (numericId !== null && numericId <= grandfatherMaxFeat) {
|
|
110
|
+
grandfathered += 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const backlog = backlogByFeature.get(featureId);
|
|
114
|
+
const qualityPath = path.join(archivedDir, featureId, '5-quality.yaml');
|
|
115
|
+
const adrPath = path.join(adrsDir, `ADR-${featureId}.md`);
|
|
116
|
+
if (!backlog) {
|
|
117
|
+
issues.push({
|
|
118
|
+
feature_id: featureId,
|
|
119
|
+
code: 'STATE_CORE_DIVERGENCE',
|
|
120
|
+
message: `${featureId} is archived but missing from .sdd/state/backlog.yaml.`,
|
|
121
|
+
backlog_path: backlogPath,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (backlog && backlog.status && !['DONE', 'ARCHIVED'].includes(backlog.status)) {
|
|
125
|
+
issues.push({
|
|
126
|
+
feature_id: featureId,
|
|
127
|
+
code: 'STATE_CORE_DIVERGENCE',
|
|
128
|
+
message: `${featureId} is archived on disk but backlog status is ${backlog.status}.`,
|
|
129
|
+
backlog_path: backlogPath,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (backlog && (backlog.frontend_impact_status ?? 'unknown') === 'unknown') {
|
|
133
|
+
issues.push({
|
|
134
|
+
feature_id: featureId,
|
|
135
|
+
code: 'FRONTEND_IMPACT_UNKNOWN',
|
|
136
|
+
message: `${featureId} has frontend_impact_status=unknown in backlog state.`,
|
|
137
|
+
backlog_path: backlogPath,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (backlog && !backlog.done_at) {
|
|
141
|
+
issues.push({
|
|
142
|
+
feature_id: featureId,
|
|
143
|
+
code: 'MISSING_DONE_AT',
|
|
144
|
+
message: `${featureId} is archived without done_at in backlog state.`,
|
|
145
|
+
backlog_path: backlogPath,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
if (backlog && !backlog.archived_at) {
|
|
149
|
+
issues.push({
|
|
150
|
+
feature_id: featureId,
|
|
151
|
+
code: 'MISSING_ARCHIVED_AT',
|
|
152
|
+
message: `${featureId} is archived without archived_at in backlog state.`,
|
|
153
|
+
backlog_path: backlogPath,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (!existsSync(adrPath)) {
|
|
157
|
+
issues.push({
|
|
158
|
+
feature_id: featureId,
|
|
159
|
+
code: 'MISSING_ADR',
|
|
160
|
+
message: `${featureId} is missing required ADR file at .sdd/core/adrs/ADR-${featureId}.md.`,
|
|
161
|
+
adr_path: adrPath,
|
|
162
|
+
});
|
|
163
|
+
issues.push({
|
|
164
|
+
feature_id: featureId,
|
|
165
|
+
code: 'STATE_CORE_DIVERGENCE',
|
|
166
|
+
message: `${featureId} has no ADR in .sdd/core/adrs while archived state exists.`,
|
|
167
|
+
adr_path: adrPath,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (!existsSync(qualityPath)) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const rawQuality = await fs.readFile(qualityPath, 'utf-8').catch(() => '');
|
|
174
|
+
if (!rawQuality.trim()) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
let quality;
|
|
178
|
+
try {
|
|
179
|
+
quality = parseWorkspaceYamlDocument('5-quality.yaml', rawQuality);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
analyzed += 1;
|
|
185
|
+
if (quality.acceptance_matrix.some((entry) => entry.status === 'not_met')) {
|
|
186
|
+
issues.push({
|
|
187
|
+
feature_id: featureId,
|
|
188
|
+
code: 'ACCEPTANCE_NOT_MET',
|
|
189
|
+
message: `${featureId} contains acceptance_matrix.status=not_met in archived quality evidence.`,
|
|
190
|
+
quality_path: qualityPath,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (quality.requirement_validation_evidence_risk_matrix.some((entry) => entry.risk_status === 'open')) {
|
|
194
|
+
issues.push({
|
|
195
|
+
feature_id: featureId,
|
|
196
|
+
code: 'OPEN_RISK',
|
|
197
|
+
message: `${featureId} contains requirement risk_status=open in archived quality evidence.`,
|
|
198
|
+
quality_path: qualityPath,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (hasPlaceholderEvidence(quality)) {
|
|
202
|
+
issues.push({
|
|
203
|
+
feature_id: featureId,
|
|
204
|
+
code: 'PLACEHOLDER_EVIDENCE',
|
|
205
|
+
message: `${featureId} still includes placeholder or future-tense evidence markers.`,
|
|
206
|
+
quality_path: qualityPath,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
generated_at: new Date().toISOString(),
|
|
212
|
+
mode,
|
|
213
|
+
recent_window_size: mode === 'recent-window' ? recentWindowSize : null,
|
|
214
|
+
scanned: orderedFeatures.length,
|
|
215
|
+
considered: consideredFeatures.length,
|
|
216
|
+
analyzed,
|
|
217
|
+
grandfathered,
|
|
218
|
+
issue_count: issues.length,
|
|
219
|
+
grandfathering: {
|
|
220
|
+
enabled: true,
|
|
221
|
+
max_feat_id: grandfatherMaxFeat,
|
|
222
|
+
policy: `Skip blocking regression checks for archived FEAT ids <= FEAT-${String(grandfatherMaxFeat).padStart(4, '0')}.`,
|
|
223
|
+
},
|
|
224
|
+
issues,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=historical-quality-regression.service.js.map
|
|
@@ -224,7 +224,7 @@ export class IngestDepositoService {
|
|
|
224
224
|
'frontend-extractor-sdd',
|
|
225
225
|
'planning-normalizer-sdd',
|
|
226
226
|
],
|
|
227
|
-
recommended_prompt: relProjectPath(paths, path.join(paths.promptsDir, '01-ingestao-deposito.md')),
|
|
227
|
+
recommended_prompt: relProjectPath(paths, path.join(paths.promptsDir, config.layout === 'en-US' ? '01-source-intake.md' : '01-ingestao-deposito.md')),
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
230
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type WorkspaceChangelogDocument, type WorkspacePlanDocument, type WorkspaceQualityDocument, type WorkspaceSpecDocument, type WorkspaceTasksDocument } from '../workspace-schemas.js';
|
|
2
|
+
export type PlanningExecutionCoherenceIssueCode = 'MISSING_FILE_EVIDENCE' | 'MISSING_COMMAND_EVIDENCE' | 'ACCEPTANCE_NOT_MET' | 'RISK_STATUS_OPEN' | 'ADR_MISSING';
|
|
3
|
+
export interface PlanningExecutionCoherenceIssue {
|
|
4
|
+
code: PlanningExecutionCoherenceIssueCode;
|
|
5
|
+
severity: 'error' | 'warning';
|
|
6
|
+
message: string;
|
|
7
|
+
evidence: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface PlanningExecutionCoherenceReport {
|
|
10
|
+
schema_version: 1;
|
|
11
|
+
feature_id: string;
|
|
12
|
+
generated_at: string;
|
|
13
|
+
workspace_path: string;
|
|
14
|
+
adr_path: string;
|
|
15
|
+
healthy: boolean;
|
|
16
|
+
planned: {
|
|
17
|
+
files: string[];
|
|
18
|
+
commands: string[];
|
|
19
|
+
};
|
|
20
|
+
execution: {
|
|
21
|
+
files: string[];
|
|
22
|
+
commands: string[];
|
|
23
|
+
};
|
|
24
|
+
issues: PlanningExecutionCoherenceIssue[];
|
|
25
|
+
}
|
|
26
|
+
export interface EvaluatePlanningExecutionCoherenceInput {
|
|
27
|
+
featureId: string;
|
|
28
|
+
workspacePath: string;
|
|
29
|
+
adrPath: string;
|
|
30
|
+
adrExists: boolean;
|
|
31
|
+
spec: WorkspaceSpecDocument;
|
|
32
|
+
plan: WorkspacePlanDocument;
|
|
33
|
+
tasks: WorkspaceTasksDocument;
|
|
34
|
+
changelog: WorkspaceChangelogDocument;
|
|
35
|
+
quality: WorkspaceQualityDocument;
|
|
36
|
+
changedFiles: string[];
|
|
37
|
+
}
|
|
38
|
+
export interface PlanningExecutionCoherenceOptions {
|
|
39
|
+
changedFiles?: string[];
|
|
40
|
+
}
|
|
41
|
+
export declare function evaluatePlanningExecutionCoherence(input: EvaluatePlanningExecutionCoherenceInput): PlanningExecutionCoherenceReport;
|
|
42
|
+
export declare class PlanningExecutionCoherenceService {
|
|
43
|
+
execute(projectRoot: string, featureId: string, options?: PlanningExecutionCoherenceOptions): Promise<PlanningExecutionCoherenceReport>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=planning-execution-coherence.service.d.ts.map
|