sdd-mcp-server 5.3.0 → 5.5.0
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/CHANGELOG.md +49 -0
- package/README.md +85 -13
- package/agents/security-auditor.md +2 -2
- package/dist/__tests__/helpers/message-clarity.d.ts +15 -0
- package/dist/__tests__/helpers/message-clarity.js +26 -0
- package/dist/__tests__/helpers/message-clarity.js.map +1 -0
- package/dist/__tests__/helpers/message-extractor.d.ts +22 -0
- package/dist/__tests__/helpers/message-extractor.js +135 -0
- package/dist/__tests__/helpers/message-extractor.js.map +1 -0
- package/dist/__tests__/unit/jev/helpers.d.ts +84 -0
- package/dist/__tests__/unit/jev/helpers.js +188 -0
- package/dist/__tests__/unit/jev/helpers.js.map +1 -0
- package/dist/adapters/cli/SDDToolAdapter.d.ts +5 -1
- package/dist/adapters/cli/SDDToolAdapter.js +22 -3
- package/dist/adapters/cli/SDDToolAdapter.js.map +1 -1
- package/dist/application/services/JevAdvisoryService.d.ts +53 -0
- package/dist/application/services/JevAdvisoryService.js +310 -0
- package/dist/application/services/JevAdvisoryService.js.map +1 -0
- package/dist/application/services/JevBaseRef.d.ts +21 -0
- package/dist/application/services/JevBaseRef.js +46 -0
- package/dist/application/services/JevBaseRef.js.map +1 -0
- package/dist/application/services/JevChangeManifest.d.ts +33 -0
- package/dist/application/services/JevChangeManifest.js +208 -0
- package/dist/application/services/JevChangeManifest.js.map +1 -0
- package/dist/application/services/JevCheckTypes.d.ts +74 -0
- package/dist/application/services/JevCheckTypes.js +2 -0
- package/dist/application/services/JevCheckTypes.js.map +1 -0
- package/dist/application/services/JevCodeExcerpts.d.ts +23 -0
- package/dist/application/services/JevCodeExcerpts.js +102 -0
- package/dist/application/services/JevCodeExcerpts.js.map +1 -0
- package/dist/application/services/JevCommitLookup.d.ts +17 -0
- package/dist/application/services/JevCommitLookup.js +72 -0
- package/dist/application/services/JevCommitLookup.js.map +1 -0
- package/dist/application/services/JevExchange.d.ts +48 -0
- package/dist/application/services/JevExchange.js +119 -0
- package/dist/application/services/JevExchange.js.map +1 -0
- package/dist/application/services/JevQuestionCatalog.d.ts +28 -0
- package/dist/application/services/JevQuestionCatalog.js +194 -0
- package/dist/application/services/JevQuestionCatalog.js.map +1 -0
- package/dist/application/services/JevRedaction.d.ts +12 -0
- package/dist/application/services/JevRedaction.js +56 -0
- package/dist/application/services/JevRedaction.js.map +1 -0
- package/dist/application/services/JevReport.d.ts +41 -0
- package/dist/application/services/JevReport.js +101 -0
- package/dist/application/services/JevReport.js.map +1 -0
- package/dist/application/services/JevResponseValidator.d.ts +34 -0
- package/dist/application/services/JevResponseValidator.js +98 -0
- package/dist/application/services/JevResponseValidator.js.map +1 -0
- package/dist/application/services/JevResults.d.ts +3 -0
- package/dist/application/services/JevResults.js +7 -0
- package/dist/application/services/JevResults.js.map +1 -0
- package/dist/application/services/JevReviewCheck.d.ts +20 -0
- package/dist/application/services/JevReviewCheck.js +120 -0
- package/dist/application/services/JevReviewCheck.js.map +1 -0
- package/dist/application/services/JevSettings.d.ts +18 -0
- package/dist/application/services/JevSettings.js +72 -0
- package/dist/application/services/JevSettings.js.map +1 -0
- package/dist/application/services/JevSidecarStore.d.ts +68 -0
- package/dist/application/services/JevSidecarStore.js +201 -0
- package/dist/application/services/JevSidecarStore.js.map +1 -0
- package/dist/application/services/JevSpecState.d.ts +38 -0
- package/dist/application/services/JevSpecState.js +109 -0
- package/dist/application/services/JevSpecState.js.map +1 -0
- package/dist/application/services/JevTestcaseState.d.ts +22 -0
- package/dist/application/services/JevTestcaseState.js +85 -0
- package/dist/application/services/JevTestcaseState.js.map +1 -0
- package/dist/application/services/RedEvidenceClassifier.d.ts +10 -0
- package/dist/application/services/RedEvidenceClassifier.js +32 -0
- package/dist/application/services/RedEvidenceClassifier.js.map +1 -0
- package/dist/application/services/WorkflowEngineService.d.ts +28 -0
- package/dist/application/services/WorkflowEngineService.js +111 -63
- package/dist/application/services/WorkflowEngineService.js.map +1 -1
- package/dist/application/services/WorkflowValidationService.d.ts +16 -0
- package/dist/application/services/WorkflowValidationService.js +39 -34
- package/dist/application/services/WorkflowValidationService.js.map +1 -1
- package/dist/cli/install-skills.js +0 -0
- package/dist/cli/install-target.d.ts +8 -6
- package/dist/cli/install-target.js +11 -3
- package/dist/cli/install-target.js.map +1 -1
- package/dist/cli/tool-support/root-guidance.js +1 -0
- package/dist/cli/tool-support/root-guidance.js.map +1 -1
- package/dist/cli/tool-support/target-installer.d.ts +1 -0
- package/dist/cli/tool-support/target-installer.js +9 -5
- package/dist/cli/tool-support/target-installer.js.map +1 -1
- package/dist/domain/ports.d.ts +34 -0
- package/dist/domain/ports.js +11 -1
- package/dist/domain/ports.js.map +1 -1
- package/dist/infrastructure/adapters/FetchJevHttpAdapter.d.ts +4 -0
- package/dist/infrastructure/adapters/FetchJevHttpAdapter.js +73 -0
- package/dist/infrastructure/adapters/FetchJevHttpAdapter.js.map +1 -0
- package/dist/infrastructure/adapters/NodeGitAdapter.d.ts +5 -0
- package/dist/infrastructure/adapters/NodeGitAdapter.js +31 -0
- package/dist/infrastructure/adapters/NodeGitAdapter.js.map +1 -0
- package/dist/infrastructure/di/container.js +10 -0
- package/dist/infrastructure/di/container.js.map +1 -1
- package/dist/infrastructure/di/types.d.ts +4 -0
- package/dist/infrastructure/di/types.js +4 -0
- package/dist/infrastructure/di/types.js.map +1 -1
- package/dist/infrastructure/mcp/sddToolDefinitions.d.ts +1 -1
- package/dist/infrastructure/mcp/sddToolDefinitions.js +72 -1
- package/dist/infrastructure/mcp/sddToolDefinitions.js.map +1 -1
- package/dist/shared/version.d.ts +1 -1
- package/dist/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/output-clarity-ladder/SKILL.md +43 -0
- package/skills/output-clarity-ladder/references/ja.md +30 -0
- package/skills/output-clarity-ladder/references/zh-TW.md +30 -0
- package/skills/sdd-commit/SKILL.md +4 -3
- package/skills/sdd-design/SKILL.md +1 -1
- package/skills/sdd-implement/SKILL.md +4 -4
- package/skills/sdd-requirements/SKILL.md +6 -6
- package/skills/sdd-review/SKILL.md +3 -2
- package/skills/sdd-security-check/SKILL.md +3 -2
- package/skills/sdd-tasks/REFERENCE.md +4 -0
- package/skills/sdd-tasks/SKILL.md +8 -6
- package/templates/CLAUDE.md +1 -1
- package/templates/codex-AGENTS.md +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { buildExcerpts } from './JevCodeExcerpts.js';
|
|
3
|
+
import { buildPrincipleQuestions, buildSecurityQuestions, QUESTION_SET_VERSION } from './JevQuestionCatalog.js';
|
|
4
|
+
import { flagSecurity, violatedPrinciples } from './JevReport.js';
|
|
5
|
+
import { skippedResult } from './JevResults.js';
|
|
6
|
+
import { fitState } from './JevSpecState.js';
|
|
7
|
+
import { redactThenClip } from './JevRedaction.js';
|
|
8
|
+
const TEXT_LIMIT = 1000;
|
|
9
|
+
function clip(value, limit, counts) {
|
|
10
|
+
const result = redactThenClip(value, limit);
|
|
11
|
+
counts.n += result.count;
|
|
12
|
+
return result.text;
|
|
13
|
+
}
|
|
14
|
+
const sha256 = (value) => createHash('sha256').update(value).digest('hex');
|
|
15
|
+
/** Hash of the findings or units the caller supplied, in caller order. */
|
|
16
|
+
export function inputHash(request) {
|
|
17
|
+
return sha256(JSON.stringify((request.check === 'principles' ? request.units : request.findings) ?? []));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* B1 security triage and B2 principle adherence. Runs in the parent only, after the audit or
|
|
21
|
+
* review returned, and judges findings and units that already exist; it never adds any.
|
|
22
|
+
*/
|
|
23
|
+
export class JevReviewCheck {
|
|
24
|
+
exchange;
|
|
25
|
+
constructor(exchange) {
|
|
26
|
+
this.exchange = exchange;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The caller supplies how to build the manifest, so a working-tree review and a range review share
|
|
30
|
+
* this path, and git is not touched at all when there is nothing to judge.
|
|
31
|
+
*/
|
|
32
|
+
async run(request, context, manifestOf) {
|
|
33
|
+
const security = request.check === 'security';
|
|
34
|
+
const inputs = (security ? request.findings : request.units) ?? [];
|
|
35
|
+
if (inputs.length === 0)
|
|
36
|
+
return skippedResult(request.check, 'not-ready');
|
|
37
|
+
const manifest = await manifestOf();
|
|
38
|
+
if (manifest.status === 'invalid-base-ref')
|
|
39
|
+
return skippedResult(request.check, 'invalid-base-ref');
|
|
40
|
+
if (manifest.status === 'empty')
|
|
41
|
+
return skippedResult(request.check, 'no-changes');
|
|
42
|
+
const excerpts = await buildExcerpts(request.projectRoot, inputs.map(({ id, path, line }) => ({ id, path, line })));
|
|
43
|
+
const excerptOf = new Map(excerpts.items.map((entry) => [entry.id, entry]));
|
|
44
|
+
const counts = { n: 0 };
|
|
45
|
+
const items = security
|
|
46
|
+
? inputs.map((entry) => this.findingItem(entry, excerptOf.get(entry.id), counts))
|
|
47
|
+
: inputs.map((entry) => this.unitItem(entry, excerptOf.get(entry.id), counts));
|
|
48
|
+
const fit = fitState(security ? 'security' : 'principles', items, undefined);
|
|
49
|
+
if (fit.keptIds.length === 0)
|
|
50
|
+
return skippedResult(request.check, 'not-ready');
|
|
51
|
+
const kept = inputs.filter(({ id }) => fit.keptIds.includes(id));
|
|
52
|
+
const plan = {
|
|
53
|
+
check: request.check,
|
|
54
|
+
kind: security ? 'security' : 'principles',
|
|
55
|
+
binding: {
|
|
56
|
+
questionSetVersion: QUESTION_SET_VERSION,
|
|
57
|
+
inputSha256: inputHash(request),
|
|
58
|
+
...(manifest.status === 'ok' && { diffSha256: manifest.diffSha256 }),
|
|
59
|
+
...(manifest.status === 'ok' && manifest.range),
|
|
60
|
+
},
|
|
61
|
+
unbound: manifest.status !== 'ok',
|
|
62
|
+
baseCommit: manifest.status === 'ok' ? manifest.baseCommit : undefined,
|
|
63
|
+
state: fit.state,
|
|
64
|
+
questions: security ? buildSecurityQuestions(fit.keptIds) : buildPrincipleQuestions(fit.keptIds),
|
|
65
|
+
omittedIds: fit.omittedIds,
|
|
66
|
+
truncated: fit.truncated,
|
|
67
|
+
redactions: excerpts.redactions + fit.redactions + counts.n,
|
|
68
|
+
droppedFindingIds: excerpts.droppedFindingIds,
|
|
69
|
+
interpret: (answers) => {
|
|
70
|
+
const located = kept.map(({ id, path, line }) => ({ id, location: `${path}:${line}` }));
|
|
71
|
+
const locationOf = new Map(located.map(({ id, location }) => [id, location]));
|
|
72
|
+
const excerptHash = (id) => excerptOf.get(id)?.excerptSha256 ?? null;
|
|
73
|
+
if (security) {
|
|
74
|
+
const { flagged, items: triaged } = flagSecurity(answers, kept.map(({ id, severity }) => ({ id, modelSeverity: severity })));
|
|
75
|
+
return {
|
|
76
|
+
flagged,
|
|
77
|
+
details: triaged.map((item) => ({
|
|
78
|
+
id: item.id,
|
|
79
|
+
flagged: flagged.includes(item.id),
|
|
80
|
+
location: locationOf.get(item.id),
|
|
81
|
+
note: `JEV ${item.jevSeverity} vs model ${item.modelSeverity}${item.disagreement ? ' (disagreement)' : ''}; ${item.classification}`,
|
|
82
|
+
})),
|
|
83
|
+
items: triaged.map((item) => ({
|
|
84
|
+
id: item.id,
|
|
85
|
+
location: locationOf.get(item.id),
|
|
86
|
+
excerptSha256: excerptHash(item.id),
|
|
87
|
+
modelSeverity: item.modelSeverity,
|
|
88
|
+
jevSeverity: item.jevSeverity,
|
|
89
|
+
disagreement: item.disagreement,
|
|
90
|
+
classification: item.classification,
|
|
91
|
+
})),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const violations = violatedPrinciples(answers, located);
|
|
95
|
+
return {
|
|
96
|
+
flagged: [...new Set(violations.map(({ unitId }) => unitId))],
|
|
97
|
+
details: violations.map(({ unitId, principle, location }) => ({ id: unitId, flagged: true, principle, location })),
|
|
98
|
+
items: located.map(({ id, location }) => ({ id, location, excerptSha256: excerptHash(id) })),
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
return this.exchange.run(plan, context);
|
|
103
|
+
}
|
|
104
|
+
findingItem(entry, excerpt, counts) {
|
|
105
|
+
return {
|
|
106
|
+
id: entry.id,
|
|
107
|
+
location: `${entry.path}:${entry.line}`,
|
|
108
|
+
title: clip(entry.title, 300, counts),
|
|
109
|
+
severity: entry.severity,
|
|
110
|
+
description: clip(entry.description, TEXT_LIMIT, counts),
|
|
111
|
+
preconditions: clip(entry.preconditions, TEXT_LIMIT, counts),
|
|
112
|
+
remediation: clip(entry.remediation, TEXT_LIMIT, counts),
|
|
113
|
+
excerpt: excerpt?.excerpt ?? null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
unitItem(entry, excerpt, counts) {
|
|
117
|
+
return { id: entry.id, location: `${entry.path}:${entry.line}`, summary: clip(entry.summary, 500, counts), excerpt: excerpt?.excerpt ?? null };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=JevReviewCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JevReviewCheck.js","sourceRoot":"","sources":["../../../src/application/services/JevReviewCheck.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FileSystemPort } from '../../domain/ports.js';
|
|
2
|
+
export declare const DEFAULT_JEV_TIMEOUT_MS = 5000;
|
|
3
|
+
export interface JevSettings {
|
|
4
|
+
readonly enabled: boolean;
|
|
5
|
+
readonly hasKey: boolean;
|
|
6
|
+
readonly allowCode: boolean;
|
|
7
|
+
readonly timeoutMs: number;
|
|
8
|
+
/** The only accessor for the key; used solely at the HTTP call site. */
|
|
9
|
+
apiKey(): string | undefined;
|
|
10
|
+
}
|
|
11
|
+
type Env = Readonly<Record<string, string | undefined>>;
|
|
12
|
+
export declare function resolveJevSettings(env: Env, projectSetting: boolean | undefined): JevSettings;
|
|
13
|
+
/**
|
|
14
|
+
* Reads `.spec/sdd-config.json` once, read-only. Any problem means "no project setting".
|
|
15
|
+
* With a port, only `exists`, `stat`, and `readFile` are used, so callers can observe the reads.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readProjectJevSetting(projectRoot: string, fileSystem?: FileSystemPort): Promise<boolean | undefined>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const DEFAULT_JEV_TIMEOUT_MS = 5000;
|
|
4
|
+
const MIN_TIMEOUT_MS = 1000;
|
|
5
|
+
const MAX_TIMEOUT_MS = 15000;
|
|
6
|
+
const MAX_KEY_LENGTH = 512;
|
|
7
|
+
const MAX_CONFIG_BYTES = 16 * 1024;
|
|
8
|
+
const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
|
|
9
|
+
function isTruthy(value) {
|
|
10
|
+
return value !== undefined && TRUTHY.has(value.trim().toLowerCase());
|
|
11
|
+
}
|
|
12
|
+
function resolveEnabled(env, projectSetting) {
|
|
13
|
+
const raw = env.SDD_JEV_ENABLED?.trim().toLowerCase();
|
|
14
|
+
if (raw === undefined || raw === '')
|
|
15
|
+
return projectSetting === true;
|
|
16
|
+
// Falsy and unrecognized values both disable JEV, even when the project setting is true.
|
|
17
|
+
return TRUTHY.has(raw);
|
|
18
|
+
}
|
|
19
|
+
function resolveKey(env) {
|
|
20
|
+
const key = env.SDD_JEV_API_KEY?.trim();
|
|
21
|
+
if (!key || key.length > MAX_KEY_LENGTH)
|
|
22
|
+
return undefined;
|
|
23
|
+
return /^[\x21-\x7E]+$/.test(key) ? key : undefined;
|
|
24
|
+
}
|
|
25
|
+
function resolveTimeout(env) {
|
|
26
|
+
const raw = env.SDD_JEV_TIMEOUT_MS?.trim();
|
|
27
|
+
if (raw === undefined || !/^\d+$/.test(raw))
|
|
28
|
+
return DEFAULT_JEV_TIMEOUT_MS;
|
|
29
|
+
return Math.min(MAX_TIMEOUT_MS, Math.max(MIN_TIMEOUT_MS, Number(raw)));
|
|
30
|
+
}
|
|
31
|
+
export function resolveJevSettings(env, projectSetting) {
|
|
32
|
+
const key = resolveKey(env);
|
|
33
|
+
const settings = {
|
|
34
|
+
enabled: resolveEnabled(env, projectSetting),
|
|
35
|
+
hasKey: key !== undefined,
|
|
36
|
+
allowCode: isTruthy(env.SDD_JEV_ALLOW_CODE),
|
|
37
|
+
timeoutMs: resolveTimeout(env),
|
|
38
|
+
};
|
|
39
|
+
// Non-enumerable accessor keeps the key out of JSON.stringify, spreads, and logs.
|
|
40
|
+
Object.defineProperty(settings, 'apiKey', { value: () => key, enumerable: false });
|
|
41
|
+
Object.defineProperty(settings, 'toString', { value: () => '[JevSettings]', enumerable: false });
|
|
42
|
+
return settings;
|
|
43
|
+
}
|
|
44
|
+
function enabledFlag(parsed) {
|
|
45
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
46
|
+
return undefined;
|
|
47
|
+
const jev = parsed.jev;
|
|
48
|
+
if (typeof jev !== 'object' || jev === null || Array.isArray(jev))
|
|
49
|
+
return undefined;
|
|
50
|
+
const enabled = jev.enabled;
|
|
51
|
+
return typeof enabled === 'boolean' ? enabled : undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Reads `.spec/sdd-config.json` once, read-only. Any problem means "no project setting".
|
|
55
|
+
* With a port, only `exists`, `stat`, and `readFile` are used, so callers can observe the reads.
|
|
56
|
+
*/
|
|
57
|
+
export async function readProjectJevSetting(projectRoot, fileSystem) {
|
|
58
|
+
const configPath = path.join(path.resolve(projectRoot), '.spec', 'sdd-config.json');
|
|
59
|
+
try {
|
|
60
|
+
if (fileSystem && !(await fileSystem.exists(configPath)))
|
|
61
|
+
return undefined;
|
|
62
|
+
const info = await (fileSystem ? fileSystem.stat(configPath) : stat(configPath));
|
|
63
|
+
if (!info.isFile() || (info.size ?? 0) > MAX_CONFIG_BYTES)
|
|
64
|
+
return undefined;
|
|
65
|
+
const content = fileSystem ? await fileSystem.readFile(configPath) : await readFile(configPath, 'utf8');
|
|
66
|
+
return content.length > MAX_CONFIG_BYTES ? undefined : enabledFlag(JSON.parse(content));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=JevSettings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JevSettings.js","sourceRoot":"","sources":["../../../src/application/services/JevSettings.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { FileSystemPort, LoggerPort } from '../../domain/ports.js';
|
|
2
|
+
import type { JevAnswer, JevMeta } from './JevResponseValidator.js';
|
|
3
|
+
export type SidecarKind = 'requirements' | 'design' | 'testcases' | 'red' | 'security' | 'principles';
|
|
4
|
+
export interface SidecarBinding {
|
|
5
|
+
questionSetVersion: number;
|
|
6
|
+
revision?: number;
|
|
7
|
+
artifactSha256?: string;
|
|
8
|
+
caseListSha256?: string;
|
|
9
|
+
evidenceSha256?: string;
|
|
10
|
+
diffSha256?: string;
|
|
11
|
+
inputSha256?: string;
|
|
12
|
+
/** Committed-range reviews only: the merge base of the caller's base ref and HEAD. */
|
|
13
|
+
rangeBaseCommit?: string;
|
|
14
|
+
/** Committed-range reviews only: HEAD when the review ran. */
|
|
15
|
+
headCommit?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SidecarItem {
|
|
18
|
+
id: string;
|
|
19
|
+
location?: string;
|
|
20
|
+
excerptSha256?: string | null;
|
|
21
|
+
modelSeverity?: string;
|
|
22
|
+
jevSeverity?: string;
|
|
23
|
+
disagreement?: boolean;
|
|
24
|
+
classification?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface JevSidecar {
|
|
27
|
+
schema: 1;
|
|
28
|
+
kind: SidecarKind;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
binding: SidecarBinding;
|
|
31
|
+
status: 'answered' | 'unavailable';
|
|
32
|
+
reason?: string;
|
|
33
|
+
source: 'jev' | 'local';
|
|
34
|
+
answers?: Record<string, JevAnswer>;
|
|
35
|
+
meta?: JevMeta;
|
|
36
|
+
baseCommit?: string | null;
|
|
37
|
+
truncated: boolean;
|
|
38
|
+
omittedIds: string[];
|
|
39
|
+
droppedFindingIds: string[];
|
|
40
|
+
redactions: number;
|
|
41
|
+
items?: SidecarItem[];
|
|
42
|
+
}
|
|
43
|
+
export interface WriteResult {
|
|
44
|
+
persisted: boolean;
|
|
45
|
+
/** Project-relative POSIX path of the written sidecar. */
|
|
46
|
+
path?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare class JevSidecarStore {
|
|
49
|
+
private readonly fileSystem;
|
|
50
|
+
private readonly logger;
|
|
51
|
+
constructor(fileSystem: FileSystemPort, logger: LoggerPort);
|
|
52
|
+
fileNameFor(kind: SidecarKind, binding: SidecarBinding, taskNumber?: string): string;
|
|
53
|
+
/** Project-relative POSIX path a sidecar for this binding is (or would be) stored at. */
|
|
54
|
+
relativePath(featureName: string | undefined, kind: SidecarKind, binding: SidecarBinding, taskNumber?: string): string;
|
|
55
|
+
write(projectRoot: string, featureName: string | undefined, sidecar: JevSidecar, taskNumber?: string): Promise<WriteResult>;
|
|
56
|
+
readMatching(projectRoot: string, featureName: string | undefined, kind: SidecarKind, binding: SidecarBinding, taskNumber?: string): Promise<JevSidecar | undefined>;
|
|
57
|
+
/**
|
|
58
|
+
* Looks a working-tree review sidecar up by the full diff hash; used by the read-only commit
|
|
59
|
+
* lookup. Only a sidecar of the current question-set version and without a range binding can
|
|
60
|
+
* describe exactly the staged change.
|
|
61
|
+
*/
|
|
62
|
+
findByDiff(projectRoot: string, featureName: string | undefined, kind: 'security' | 'principles', diffSha256: string, questionSetVersion: number): Promise<JevSidecar | undefined>;
|
|
63
|
+
/** Names of review sidecars of one kind in a location; empty on any problem. */
|
|
64
|
+
listReviewSidecars(projectRoot: string, featureName: string | undefined, kind: 'security' | 'principles'): Promise<string[]>;
|
|
65
|
+
private readAnswered;
|
|
66
|
+
private reviewDir;
|
|
67
|
+
private contain;
|
|
68
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { SpecPathResolver, validateFeatureName } from './SpecPathResolver.js';
|
|
3
|
+
const MAX_SIDECAR_BYTES = 1_048_576;
|
|
4
|
+
const HASH_PREFIX = 12;
|
|
5
|
+
const KINDS = new Set(['requirements', 'design', 'testcases', 'red', 'security', 'principles']);
|
|
6
|
+
/** Reasons that must leave no filesystem trace at all. */
|
|
7
|
+
const TRACELESS_REASONS = new Set(['disabled', 'no-key']);
|
|
8
|
+
const BINDING_KEYS = [
|
|
9
|
+
'questionSetVersion', 'revision', 'artifactSha256', 'caseListSha256', 'evidenceSha256', 'diffSha256', 'inputSha256',
|
|
10
|
+
'rangeBaseCommit', 'headCommit',
|
|
11
|
+
];
|
|
12
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
14
|
+
function pickBinding(binding) {
|
|
15
|
+
const picked = {};
|
|
16
|
+
for (const key of BINDING_KEYS)
|
|
17
|
+
if (binding[key] !== undefined)
|
|
18
|
+
picked[key] = binding[key];
|
|
19
|
+
return picked;
|
|
20
|
+
}
|
|
21
|
+
function pickAnswer(answer) {
|
|
22
|
+
if (answer.type === 'choice')
|
|
23
|
+
return { type: 'choice', choice: answer.choice };
|
|
24
|
+
if (answer.type === 'score')
|
|
25
|
+
return { type: 'score', raw: answer.raw, index: answer.index };
|
|
26
|
+
return { type: 'noul', noul: answer.noul };
|
|
27
|
+
}
|
|
28
|
+
function pickItem(item) {
|
|
29
|
+
return {
|
|
30
|
+
id: item.id, location: item.location, excerptSha256: item.excerptSha256, modelSeverity: item.modelSeverity,
|
|
31
|
+
jevSeverity: item.jevSeverity, disagreement: item.disagreement, classification: item.classification,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Whitelist serialization: unknown fields (a key, a request body, excerpt text) can never be written. */
|
|
35
|
+
function toStored(sidecar) {
|
|
36
|
+
return {
|
|
37
|
+
schema: 1,
|
|
38
|
+
kind: sidecar.kind,
|
|
39
|
+
createdAt: sidecar.createdAt,
|
|
40
|
+
binding: pickBinding(sidecar.binding),
|
|
41
|
+
status: sidecar.status,
|
|
42
|
+
reason: sidecar.reason,
|
|
43
|
+
source: sidecar.source,
|
|
44
|
+
answers: sidecar.answers && Object.fromEntries(Object.entries(sidecar.answers).map(([id, answer]) => [id, pickAnswer(answer)])),
|
|
45
|
+
meta: sidecar.meta && Object.fromEntries(Object.entries(sidecar.meta).filter(([, value]) => value === null || ['string', 'number', 'boolean'].includes(typeof value))),
|
|
46
|
+
baseCommit: sidecar.baseCommit,
|
|
47
|
+
truncated: sidecar.truncated,
|
|
48
|
+
omittedIds: [...sidecar.omittedIds],
|
|
49
|
+
droppedFindingIds: [...sidecar.droppedFindingIds],
|
|
50
|
+
redactions: sidecar.redactions,
|
|
51
|
+
items: sidecar.items?.map(pickItem),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function isAnswer(value) {
|
|
55
|
+
if (!isRecord(value))
|
|
56
|
+
return false;
|
|
57
|
+
if (value.type === 'choice')
|
|
58
|
+
return typeof value.choice === 'string';
|
|
59
|
+
if (value.type === 'score')
|
|
60
|
+
return typeof value.raw === 'number' && Number.isFinite(value.raw) && Number.isInteger(value.index);
|
|
61
|
+
return value.type === 'noul' && typeof value.noul === 'number';
|
|
62
|
+
}
|
|
63
|
+
function hasValidHeader(value) {
|
|
64
|
+
return value.schema === 1
|
|
65
|
+
&& typeof value.kind === 'string' && KINDS.has(value.kind)
|
|
66
|
+
&& typeof value.createdAt === 'string'
|
|
67
|
+
&& isRecord(value.binding) && typeof value.binding.questionSetVersion === 'number'
|
|
68
|
+
&& (value.status === 'answered' || value.status === 'unavailable')
|
|
69
|
+
&& (value.source === 'jev' || value.source === 'local');
|
|
70
|
+
}
|
|
71
|
+
function hasValidBody(value) {
|
|
72
|
+
return typeof value.truncated === 'boolean'
|
|
73
|
+
&& typeof value.redactions === 'number'
|
|
74
|
+
&& isStringArray(value.omittedIds)
|
|
75
|
+
&& isStringArray(value.droppedFindingIds)
|
|
76
|
+
&& (value.answers === undefined || (isRecord(value.answers) && Object.values(value.answers).every(isAnswer)));
|
|
77
|
+
}
|
|
78
|
+
function isSidecar(value) {
|
|
79
|
+
return isRecord(value) && hasValidHeader(value) && hasValidBody(value);
|
|
80
|
+
}
|
|
81
|
+
function sameBinding(stored, current) {
|
|
82
|
+
return BINDING_KEYS.every((key) => stored[key] === current[key]);
|
|
83
|
+
}
|
|
84
|
+
export class JevSidecarStore {
|
|
85
|
+
fileSystem;
|
|
86
|
+
logger;
|
|
87
|
+
constructor(fileSystem, logger) {
|
|
88
|
+
this.fileSystem = fileSystem;
|
|
89
|
+
this.logger = logger;
|
|
90
|
+
}
|
|
91
|
+
fileNameFor(kind, binding, taskNumber) {
|
|
92
|
+
if (kind === 'red') {
|
|
93
|
+
const task = (taskNumber ?? '').replace(/[^0-9.]/g, '') || 'task';
|
|
94
|
+
return `jev-red-${task}-${(binding.evidenceSha256 ?? '').slice(0, HASH_PREFIX)}.json`;
|
|
95
|
+
}
|
|
96
|
+
if (kind === 'security' || kind === 'principles') {
|
|
97
|
+
const range = binding.rangeBaseCommit === undefined ? '' : 'range-';
|
|
98
|
+
return `jev-review-${kind}-${range}${(binding.diffSha256 ?? '').slice(0, HASH_PREFIX)}.json`;
|
|
99
|
+
}
|
|
100
|
+
return `jev-${kind}-r${binding.revision ?? 0}.json`;
|
|
101
|
+
}
|
|
102
|
+
/** Project-relative POSIX path a sidecar for this binding is (or would be) stored at. */
|
|
103
|
+
relativePath(featureName, kind, binding, taskNumber) {
|
|
104
|
+
const dir = featureName === undefined ? '.spec/reviews' : `.spec/specs/${featureName}/review`;
|
|
105
|
+
return `${dir}/${this.fileNameFor(kind, binding, taskNumber)}`;
|
|
106
|
+
}
|
|
107
|
+
async write(projectRoot, featureName, sidecar, taskNumber) {
|
|
108
|
+
if (sidecar.status === 'unavailable' && sidecar.reason !== undefined && TRACELESS_REASONS.has(sidecar.reason)) {
|
|
109
|
+
return { persisted: false };
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const root = path.resolve(projectRoot);
|
|
113
|
+
const dir = this.reviewDir(root, featureName);
|
|
114
|
+
if (featureName !== undefined && !(await this.fileSystem.exists(path.join(root, '.spec', 'specs', featureName)))) {
|
|
115
|
+
return { persisted: false };
|
|
116
|
+
}
|
|
117
|
+
await this.contain(root, dir);
|
|
118
|
+
const fileName = this.fileNameFor(sidecar.kind, sidecar.binding, taskNumber);
|
|
119
|
+
const file = path.join(dir, fileName);
|
|
120
|
+
const content = `${JSON.stringify(toStored(sidecar), null, 2)}\n`;
|
|
121
|
+
if (Buffer.byteLength(content, 'utf8') > MAX_SIDECAR_BYTES)
|
|
122
|
+
return { persisted: false };
|
|
123
|
+
if (sidecar.status === 'unavailable' && (await this.readAnswered(projectRoot, featureName, sidecar.kind, fileName))) {
|
|
124
|
+
return { persisted: false };
|
|
125
|
+
}
|
|
126
|
+
await this.fileSystem.mkdir(dir);
|
|
127
|
+
await this.contain(root, dir);
|
|
128
|
+
await this.contain(root, file);
|
|
129
|
+
if (this.fileSystem.writeFileAtomic)
|
|
130
|
+
await this.fileSystem.writeFileAtomic(file, content);
|
|
131
|
+
else
|
|
132
|
+
await this.fileSystem.writeFile(file, content);
|
|
133
|
+
return { persisted: true, path: path.relative(root, file).split(path.sep).join('/') };
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
this.logger.warn('JEV sidecar not persisted', { kind: sidecar.kind, errorName: error instanceof Error ? error.name : 'Error' });
|
|
137
|
+
return { persisted: false };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async readMatching(projectRoot, featureName, kind, binding, taskNumber) {
|
|
141
|
+
const parsed = await this.readAnswered(projectRoot, featureName, kind, this.fileNameFor(kind, binding, taskNumber));
|
|
142
|
+
return parsed && sameBinding(parsed.binding, binding) ? parsed : undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Looks a working-tree review sidecar up by the full diff hash; used by the read-only commit
|
|
146
|
+
* lookup. Only a sidecar of the current question-set version and without a range binding can
|
|
147
|
+
* describe exactly the staged change.
|
|
148
|
+
*/
|
|
149
|
+
async findByDiff(projectRoot, featureName, kind, diffSha256, questionSetVersion) {
|
|
150
|
+
const parsed = await this.readAnswered(projectRoot, featureName, kind, this.fileNameFor(kind, { questionSetVersion, diffSha256 }));
|
|
151
|
+
const bound = parsed?.binding;
|
|
152
|
+
const matches = bound?.diffSha256 === diffSha256
|
|
153
|
+
&& bound.questionSetVersion === questionSetVersion
|
|
154
|
+
&& bound.rangeBaseCommit === undefined
|
|
155
|
+
&& bound.headCommit === undefined;
|
|
156
|
+
return matches ? parsed : undefined;
|
|
157
|
+
}
|
|
158
|
+
/** Names of review sidecars of one kind in a location; empty on any problem. */
|
|
159
|
+
async listReviewSidecars(projectRoot, featureName, kind) {
|
|
160
|
+
try {
|
|
161
|
+
const root = path.resolve(projectRoot);
|
|
162
|
+
const dir = this.reviewDir(root, featureName);
|
|
163
|
+
await this.contain(root, dir);
|
|
164
|
+
if (!(await this.fileSystem.exists(dir)))
|
|
165
|
+
return [];
|
|
166
|
+
return (await this.fileSystem.readdir(dir)).filter((name) => name.startsWith(`jev-review-${kind}-`) && name.endsWith('.json'));
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async readAnswered(projectRoot, featureName, kind, fileName) {
|
|
173
|
+
try {
|
|
174
|
+
const root = path.resolve(projectRoot);
|
|
175
|
+
const dir = this.reviewDir(root, featureName);
|
|
176
|
+
const file = path.join(dir, fileName);
|
|
177
|
+
await this.contain(root, dir);
|
|
178
|
+
await this.contain(root, file);
|
|
179
|
+
if (!(await this.fileSystem.exists(file)))
|
|
180
|
+
return undefined;
|
|
181
|
+
const info = await this.fileSystem.stat(file);
|
|
182
|
+
if (!info.isFile() || (info.size ?? 0) > MAX_SIDECAR_BYTES)
|
|
183
|
+
return undefined;
|
|
184
|
+
const parsed = JSON.parse(await this.fileSystem.readFile(file));
|
|
185
|
+
return isSidecar(parsed) && parsed.kind === kind && parsed.status === 'answered' ? parsed : undefined;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
reviewDir(root, featureName) {
|
|
192
|
+
if (featureName === undefined)
|
|
193
|
+
return path.join(root, '.spec', 'reviews');
|
|
194
|
+
validateFeatureName(featureName);
|
|
195
|
+
return path.join(root, '.spec', 'specs', featureName, 'review');
|
|
196
|
+
}
|
|
197
|
+
async contain(root, target) {
|
|
198
|
+
await new SpecPathResolver(this.fileSystem).assertContained(path.join(root, '.spec'), target);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=JevSidecarStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JevSidecarStore.js","sourceRoot":"","sources":["../../../src/application/services/JevSidecarStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { JevQuestionSet } from './JevResponseValidator.js';
|
|
2
|
+
import type { AdvisorySnapshot } from './WorkflowEngineService.js';
|
|
3
|
+
/** Serialized `state` cap for spec-text requests. */
|
|
4
|
+
export declare const MAX_STATE_BYTES = 32768;
|
|
5
|
+
export interface ExchangeState {
|
|
6
|
+
kind: string;
|
|
7
|
+
items: Array<Record<string, unknown>>;
|
|
8
|
+
context?: Record<string, unknown>;
|
|
9
|
+
}
|
|
10
|
+
export interface FittedState {
|
|
11
|
+
state: ExchangeState;
|
|
12
|
+
keptIds: string[];
|
|
13
|
+
omittedIds: string[];
|
|
14
|
+
truncated: boolean;
|
|
15
|
+
redactions: number;
|
|
16
|
+
}
|
|
17
|
+
export interface BuiltExchange {
|
|
18
|
+
state: ExchangeState;
|
|
19
|
+
questions: JevQuestionSet;
|
|
20
|
+
/** Ids the answers are interpreted against, in document order. */
|
|
21
|
+
itemIds: string[];
|
|
22
|
+
/** Second id group (NFRs for the design check). */
|
|
23
|
+
secondaryIds: string[];
|
|
24
|
+
omittedIds: string[];
|
|
25
|
+
truncated: boolean;
|
|
26
|
+
redactions: number;
|
|
27
|
+
}
|
|
28
|
+
/** Redacts every item, then keeps whole items in order until the next one would exceed the cap. */
|
|
29
|
+
export declare function fitState(kind: string, items: ReadonlyArray<Record<string, unknown> & {
|
|
30
|
+
id: string;
|
|
31
|
+
}>, context: Record<string, unknown> | undefined, maxBytes?: number): FittedState;
|
|
32
|
+
export declare function buildRequirementsExchange(snapshot: AdvisorySnapshot): BuiltExchange | undefined;
|
|
33
|
+
/** RED evidence is the whole state: command, summary, test name, and the task's acceptance criteria. No source code. */
|
|
34
|
+
export declare function buildRedExchange(taskNumber: string, testName: string, evidence: {
|
|
35
|
+
command: string;
|
|
36
|
+
summary: string;
|
|
37
|
+
}, acceptanceCriteria: string): BuiltExchange | undefined;
|
|
38
|
+
export declare function buildDesignExchange(snapshot: AdvisorySnapshot): BuiltExchange | undefined;
|