@ryuenn3123/agentic-senior-core 3.0.17 → 3.0.20
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/.agent-context/prompts/bootstrap-design.md +84 -94
- package/.agent-context/prompts/init-project.md +32 -100
- package/.agent-context/prompts/refactor.md +22 -44
- package/.agent-context/prompts/review-code.md +28 -52
- package/.agent-context/review-checklists/architecture-review.md +31 -62
- package/.agent-context/review-checklists/pr-checklist.md +74 -108
- package/.agent-context/rules/api-docs.md +18 -206
- package/.agent-context/rules/architecture.md +40 -207
- package/.agent-context/rules/database-design.md +10 -199
- package/.agent-context/rules/docker-runtime.md +5 -5
- package/.agent-context/rules/efficiency-vs-hype.md +11 -149
- package/.agent-context/rules/error-handling.md +9 -231
- package/.agent-context/rules/event-driven.md +17 -221
- package/.agent-context/rules/frontend-architecture.md +66 -119
- package/.agent-context/rules/git-workflow.md +1 -1
- package/.agent-context/rules/microservices.md +28 -161
- package/.agent-context/rules/naming-conv.md +8 -138
- package/.agent-context/rules/performance.md +9 -175
- package/.agent-context/rules/realtime.md +11 -44
- package/.agent-context/rules/security.md +11 -295
- package/.agent-context/rules/testing.md +9 -174
- package/.agent-context/state/benchmark-analysis.json +3 -3
- package/.agent-context/state/memory-continuity-benchmark.json +1 -1
- package/.agent-context/state/onboarding-report.json +71 -11
- package/.agents/workflows/init-project.md +7 -24
- package/.agents/workflows/refactor.md +7 -24
- package/.agents/workflows/review-code.md +7 -24
- package/.cursorrules +22 -21
- package/.gemini/instructions.md +2 -2
- package/.github/copilot-instructions.md +2 -2
- package/.instructions.md +112 -213
- package/.windsurfrules +22 -21
- package/AGENTS.md +4 -4
- package/CONTRIBUTING.md +13 -22
- package/README.md +6 -20
- package/lib/cli/commands/init.mjs +102 -148
- package/lib/cli/commands/launch.mjs +3 -3
- package/lib/cli/commands/optimize.mjs +14 -4
- package/lib/cli/commands/upgrade.mjs +25 -23
- package/lib/cli/compiler.mjs +96 -62
- package/lib/cli/constants.mjs +28 -136
- package/lib/cli/detector/design-evidence.mjs +189 -6
- package/lib/cli/detector.mjs +6 -7
- package/lib/cli/init-detection-flow.mjs +10 -93
- package/lib/cli/init-selection.mjs +2 -68
- package/lib/cli/project-scaffolder/constants.mjs +1 -1
- package/lib/cli/project-scaffolder/design-contract.mjs +438 -335
- package/lib/cli/project-scaffolder/discovery.mjs +36 -82
- package/lib/cli/project-scaffolder/prompt-builders.mjs +55 -63
- package/lib/cli/project-scaffolder/storage.mjs +0 -4
- package/lib/cli/token-optimization.mjs +1 -1
- package/lib/cli/utils.mjs +75 -9
- package/package.json +2 -2
- package/scripts/detection-benchmark.mjs +4 -15
- package/scripts/documentation-boundary-audit.mjs +9 -9
- package/scripts/explain-on-demand-audit.mjs +11 -11
- package/scripts/forbidden-content-check.mjs +9 -9
- package/scripts/frontend-usability-audit.mjs +57 -36
- package/scripts/llm-judge.mjs +1 -1
- package/scripts/mcp-server/constants.mjs +60 -0
- package/scripts/mcp-server/tool-registry.mjs +149 -0
- package/scripts/mcp-server/tools.mjs +446 -0
- package/scripts/mcp-server.mjs +23 -661
- package/scripts/release-gate/audit-checks.mjs +426 -0
- package/scripts/release-gate/constants.mjs +53 -0
- package/scripts/release-gate/runtime.mjs +63 -0
- package/scripts/release-gate/static-checks.mjs +182 -0
- package/scripts/release-gate.mjs +13 -794
- package/scripts/rules-guardian-audit.mjs +14 -13
- package/scripts/single-source-lazy-loading-audit.mjs +3 -3
- package/scripts/sync-thin-adapters.mjs +5 -5
- package/scripts/ui-design-judge/constants.mjs +24 -0
- package/scripts/ui-design-judge/design-execution-summary.mjs +259 -0
- package/scripts/ui-design-judge/git-input.mjs +131 -0
- package/scripts/ui-design-judge/prompting.mjs +73 -0
- package/scripts/ui-design-judge/providers.mjs +102 -0
- package/scripts/ui-design-judge/reporting.mjs +182 -0
- package/scripts/ui-design-judge/rubric-calibration.mjs +214 -0
- package/scripts/ui-design-judge/rubric-goldset.json +188 -0
- package/scripts/ui-design-judge.mjs +166 -771
- package/scripts/ui-rubric-calibration.mjs +35 -0
- package/scripts/validate/config.mjs +198 -55
- package/scripts/validate/coverage-checks.mjs +32 -7
- package/scripts/validate.mjs +8 -4
- package/lib/cli/architect.mjs +0 -431
|
@@ -36,15 +36,15 @@ const SUPPORTED_WORKFLOWS = new Set([
|
|
|
36
36
|
const CORE_PATTERN_SIGNALS = [
|
|
37
37
|
{
|
|
38
38
|
pattern: 'layer-separation',
|
|
39
|
-
snippet: '
|
|
39
|
+
snippet: '## Layer Boundaries (Mandatory)',
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
pattern: '
|
|
43
|
-
snippet: '
|
|
42
|
+
pattern: 'agent-decides-topology',
|
|
43
|
+
snippet: 'Do not force a default architecture label before the repo, delivery model, and boundary evidence are clear.',
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
pattern: 'feature-based-grouping',
|
|
47
|
-
snippet: '
|
|
47
|
+
snippet: 'Group code by feature or domain',
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
pattern: 'rules-as-guardian-cross-session',
|
|
@@ -55,7 +55,7 @@ const CORE_PATTERN_SIGNALS = [
|
|
|
55
55
|
const REQUIRED_ARCHITECTURE_RULE_SNIPPETS = [
|
|
56
56
|
'## Rules as Guardian (Cross-Session Consistency)',
|
|
57
57
|
'Session handoff must include active architecture contract summary.',
|
|
58
|
-
'Detect drift before changing
|
|
58
|
+
'Detect drift before changing runtime choices, topology, public contracts, or core patterns.',
|
|
59
59
|
'Direction changes require explicit user confirmation before applying changes.',
|
|
60
60
|
];
|
|
61
61
|
|
|
@@ -66,7 +66,8 @@ const REQUIRED_PR_CHECKLIST_SNIPPETS = [
|
|
|
66
66
|
];
|
|
67
67
|
|
|
68
68
|
const REQUIRED_REVIEW_PROMPT_SNIPPETS = [
|
|
69
|
-
'
|
|
69
|
+
'Review the code with a production-risk mindset.',
|
|
70
|
+
'Do not invent stack-specific concerns unless the repo or changed files prove they apply.',
|
|
70
71
|
];
|
|
71
72
|
|
|
72
73
|
function pushResult(results, isPassed, checkName, details) {
|
|
@@ -295,8 +296,8 @@ function parseBooleanFromEnvironment(rawEnvironmentValue) {
|
|
|
295
296
|
|
|
296
297
|
function buildArchitectureContract(onboardingReport, corePatterns) {
|
|
297
298
|
return {
|
|
298
|
-
|
|
299
|
-
|
|
299
|
+
runtimeDecision: String(onboardingReport?.runtimeDecision?.mode || onboardingReport?.selectedStack || 'unknown').trim() || 'unknown',
|
|
300
|
+
architectureDecision: String(onboardingReport?.architectureDecision?.mode || onboardingReport?.selectedBlueprint || 'unknown').trim() || 'unknown',
|
|
300
301
|
profile: String(onboardingReport?.selectedProfile || 'unknown').trim() || 'unknown',
|
|
301
302
|
corePatterns: normalizeCorePatterns(corePatterns),
|
|
302
303
|
};
|
|
@@ -312,7 +313,7 @@ function toComparableContractValue(value) {
|
|
|
312
313
|
|
|
313
314
|
function detectContractDrift(baseContract, targetContract, driftSource) {
|
|
314
315
|
const driftItems = [];
|
|
315
|
-
const fieldNames = ['
|
|
316
|
+
const fieldNames = ['runtimeDecision', 'architectureDecision', 'profile', 'corePatterns'];
|
|
316
317
|
|
|
317
318
|
for (const fieldName of fieldNames) {
|
|
318
319
|
const baseValue = toComparableContractValue(baseContract?.[fieldName]);
|
|
@@ -336,7 +337,7 @@ function buildSessionHandoffSummary(architectureContract) {
|
|
|
336
337
|
? architectureContract.corePatterns.join(', ')
|
|
337
338
|
: 'none';
|
|
338
339
|
|
|
339
|
-
return `Architecture contract summary:
|
|
340
|
+
return `Architecture contract summary: runtimeDecision=${architectureContract.runtimeDecision}, architectureDecision=${architectureContract.architectureDecision}, profile=${architectureContract.profile}, corePatterns=${corePatternsSummary}.`;
|
|
340
341
|
}
|
|
341
342
|
|
|
342
343
|
function assertSnippetCoverage(sourceLabel, sourcePath, requiredSnippets, failures, results) {
|
|
@@ -468,8 +469,8 @@ function runAudit() {
|
|
|
468
469
|
);
|
|
469
470
|
|
|
470
471
|
const proposedContract = {
|
|
471
|
-
|
|
472
|
-
|
|
472
|
+
runtimeDecision: parsedArguments.proposedStack || activeContract.runtimeDecision,
|
|
473
|
+
architectureDecision: parsedArguments.proposedBlueprint || activeContract.architectureDecision,
|
|
473
474
|
profile: activeContract.profile,
|
|
474
475
|
corePatterns: parsedArguments.proposedCorePatterns.length > 0
|
|
475
476
|
? parsedArguments.proposedCorePatterns
|
|
@@ -573,4 +574,4 @@ function runAudit() {
|
|
|
573
574
|
process.exit(reportPayload.passed ? 0 : 1);
|
|
574
575
|
}
|
|
575
576
|
|
|
576
|
-
runAudit();
|
|
577
|
+
runAudit();
|
|
@@ -76,8 +76,8 @@ const REQUIRED_REVIEW_PROMPT_SNIPPETS = [
|
|
|
76
76
|
|
|
77
77
|
const REQUIRED_COMPILER_SNIPPETS = [
|
|
78
78
|
'## LAYER 2 POLICY: LAZY RULE LOADING',
|
|
79
|
-
'Load
|
|
80
|
-
'Avoid eager loading unrelated
|
|
79
|
+
'Load runtime-specific guidance only when task scope touches that runtime.',
|
|
80
|
+
'Avoid eager loading unrelated runtime guidance to prevent instruction conflicts.',
|
|
81
81
|
"stackLoadingMode: 'lazy'",
|
|
82
82
|
];
|
|
83
83
|
|
|
@@ -532,4 +532,4 @@ function runAudit() {
|
|
|
532
532
|
process.exit(reportPayload.passed ? 0 : 1);
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
-
runAudit();
|
|
535
|
+
runAudit();
|
|
@@ -45,7 +45,7 @@ If your host stops at this file instead of following the full chain, obey the Cr
|
|
|
45
45
|
- For UI, UX, layout, screen, tailwind, frontend, or redesign requests: load [.agent-context/prompts/bootstrap-design.md](.agent-context/prompts/bootstrap-design.md) and [.agent-context/rules/frontend-architecture.md](.agent-context/rules/frontend-architecture.md) before editing code.
|
|
46
46
|
- For UI scope: if \`docs/DESIGN.md\` or \`docs/design-intent.json\` is missing, materialize or refine them before implementing UI changes.
|
|
47
47
|
- For refactor, improve, clean up, or fix requests: inspect the active rules and propose a plan before editing.
|
|
48
|
-
- For new project or module requests:
|
|
48
|
+
- For new project or module requests: clarify constraints, stack decisions, and required docs before generating code.
|
|
49
49
|
- For ecosystem, framework, dependency, or Docker claims: perform live web research instead of relying on stale local heuristics.
|
|
50
50
|
|
|
51
51
|
## Mandatory Bootstrap Chain
|
|
@@ -57,11 +57,11 @@ If your host stops at this file instead of following the full chain, obey the Cr
|
|
|
57
57
|
5. Enforce review contracts from [.agent-context/review-checklists/](.agent-context/review-checklists).
|
|
58
58
|
6. Read change-risk maps and continuity state from [.agent-context/state/](.agent-context/state).
|
|
59
59
|
7. Enforce policy thresholds from [.agent-context/policies/](.agent-context/policies).
|
|
60
|
-
8. Use dynamic stack and
|
|
60
|
+
8. Use dynamic stack, structure, and live research signals from project context docs.
|
|
61
61
|
|
|
62
62
|
## Trigger Rules
|
|
63
63
|
|
|
64
|
-
- New project or module requests: propose
|
|
64
|
+
- New project or module requests: propose scope, constraints, and required docs first, then wait for approval.
|
|
65
65
|
- Refactor or fix requests: propose plan first, then execute safely.
|
|
66
66
|
- Completion: run [.agent-context/review-checklists/pr-checklist.md](.agent-context/review-checklists/pr-checklist.md) before declaring done.
|
|
67
67
|
|
|
@@ -92,7 +92,7 @@ If your host stops at this file, follow this minimum floor:
|
|
|
92
92
|
4. Load request templates from [.agent-context/prompts/](../.agent-context/prompts).
|
|
93
93
|
5. Apply review contracts from [.agent-context/review-checklists/](../.agent-context/review-checklists).
|
|
94
94
|
6. Apply state awareness from [.agent-context/state/](../.agent-context/state) and thresholds from [.agent-context/policies/](../.agent-context/policies).
|
|
95
|
-
7. Resolve stack and
|
|
95
|
+
7. Resolve stack, structure, and dependency choices from project context docs plus live evidence.
|
|
96
96
|
|
|
97
97
|
## Completion Gate
|
|
98
98
|
|
|
@@ -123,7 +123,7 @@ If your host stops at this file, follow this minimum floor:
|
|
|
123
123
|
4. Load request templates from [.agent-context/prompts/](../.agent-context/prompts).
|
|
124
124
|
5. Apply review contracts from [.agent-context/review-checklists/](../.agent-context/review-checklists).
|
|
125
125
|
6. Apply state awareness from [.agent-context/state/](../.agent-context/state) and policy thresholds from [.agent-context/policies/](../.agent-context/policies).
|
|
126
|
-
7. Resolve stack and
|
|
126
|
+
7. Resolve stack, structure, and dependency choices from project context docs plus live evidence.
|
|
127
127
|
|
|
128
128
|
## Completion Gate
|
|
129
129
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export const REPOSITORY_ROOT = resolve(__dirname, '..', '..');
|
|
10
|
+
export const DESIGN_INTENT_PATH = resolve(REPOSITORY_ROOT, 'docs', 'design-intent.json');
|
|
11
|
+
export const DESIGN_GUIDE_PATH = resolve(REPOSITORY_ROOT, 'docs', 'DESIGN.md');
|
|
12
|
+
export const MAX_DIFF_CHARS = 12000;
|
|
13
|
+
export const UI_FILE_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.vue', '.css', '.scss', '.sass']);
|
|
14
|
+
export const DESIGN_EXECUTION_REQUIRED_CAPABILITIES = [
|
|
15
|
+
'requireSurfacePlan',
|
|
16
|
+
'requireComponentGraph',
|
|
17
|
+
'requireViewportMutationPlan',
|
|
18
|
+
'requireInteractionStateMatrix',
|
|
19
|
+
'requireContentPriorityMap',
|
|
20
|
+
'requireTaskFlowNarrative',
|
|
21
|
+
'requireSignatureMoveRationale',
|
|
22
|
+
'requireStructuredHandoff',
|
|
23
|
+
'requireRepoEvidenceAlignment',
|
|
24
|
+
];
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import {
|
|
5
|
+
DESIGN_EXECUTION_REQUIRED_CAPABILITIES,
|
|
6
|
+
DESIGN_GUIDE_PATH,
|
|
7
|
+
DESIGN_INTENT_PATH,
|
|
8
|
+
} from './constants.mjs';
|
|
9
|
+
|
|
10
|
+
export function normalizeStringArray(rawValue) {
|
|
11
|
+
if (!Array.isArray(rawValue)) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return rawValue
|
|
16
|
+
.map((entryValue) => String(entryValue || '').trim())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadDesignIntent() {
|
|
21
|
+
if (!existsSync(DESIGN_INTENT_PATH)) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(readFileSync(DESIGN_INTENT_PATH, 'utf8'));
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function loadDesignGuide() {
|
|
33
|
+
if (!existsSync(DESIGN_GUIDE_PATH)) {
|
|
34
|
+
return '';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return readFileSync(DESIGN_GUIDE_PATH, 'utf8');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasRepoEvidenceSummary(designIntentContent) {
|
|
41
|
+
return Boolean(
|
|
42
|
+
designIntentContent?.repoEvidence?.designEvidenceSummary
|
|
43
|
+
&& typeof designIntentContent.repoEvidence.designEvidenceSummary === 'object'
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasStructuredInspectionEvidence(designIntentContent) {
|
|
48
|
+
return Boolean(
|
|
49
|
+
designIntentContent?.repoEvidence?.designEvidenceSummary?.structuredInspection
|
|
50
|
+
&& typeof designIntentContent.repoEvidence.designEvidenceSummary.structuredInspection === 'object'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function summarizeDesignExecutionHandoff(designIntentContent) {
|
|
55
|
+
const designExecutionHandoff = designIntentContent?.designExecutionHandoff
|
|
56
|
+
&& typeof designIntentContent.designExecutionHandoff === 'object'
|
|
57
|
+
? designIntentContent.designExecutionHandoff
|
|
58
|
+
: {};
|
|
59
|
+
|
|
60
|
+
const surfacePlan = Array.isArray(designExecutionHandoff.surfacePlan)
|
|
61
|
+
? designExecutionHandoff.surfacePlan
|
|
62
|
+
: [];
|
|
63
|
+
const componentGraphNodes = Array.isArray(designExecutionHandoff.componentGraph?.nodes)
|
|
64
|
+
? designExecutionHandoff.componentGraph.nodes
|
|
65
|
+
: [];
|
|
66
|
+
const componentGraphEdges = Array.isArray(designExecutionHandoff.componentGraph?.edges)
|
|
67
|
+
? designExecutionHandoff.componentGraph.edges
|
|
68
|
+
: [];
|
|
69
|
+
const interactionStateMatrix = Array.isArray(designExecutionHandoff.interactionStateMatrix)
|
|
70
|
+
? designExecutionHandoff.interactionStateMatrix
|
|
71
|
+
: [];
|
|
72
|
+
const taskFlowNarrative = normalizeStringArray(designExecutionHandoff.taskFlowNarrative);
|
|
73
|
+
const contentPriorityMap = designExecutionHandoff.contentPriorityMap
|
|
74
|
+
&& typeof designExecutionHandoff.contentPriorityMap === 'object'
|
|
75
|
+
? designExecutionHandoff.contentPriorityMap
|
|
76
|
+
: {};
|
|
77
|
+
const viewportMutationPlan = designExecutionHandoff.viewportMutationPlan
|
|
78
|
+
&& typeof designExecutionHandoff.viewportMutationPlan === 'object'
|
|
79
|
+
? designExecutionHandoff.viewportMutationPlan
|
|
80
|
+
: {};
|
|
81
|
+
|
|
82
|
+
function hasViewportMutationEntry(viewportKey) {
|
|
83
|
+
const viewportEntry = viewportMutationPlan?.[viewportKey];
|
|
84
|
+
return Boolean(
|
|
85
|
+
viewportEntry
|
|
86
|
+
&& typeof viewportEntry === 'object'
|
|
87
|
+
&& String(viewportEntry.primaryOperation || '').trim().length > 0
|
|
88
|
+
&& Array.isArray(viewportEntry.requiredSurfaceActions)
|
|
89
|
+
&& viewportEntry.requiredSurfaceActions.length > 0
|
|
90
|
+
&& Array.isArray(viewportEntry.forbiddenPatterns)
|
|
91
|
+
&& viewportEntry.forbiddenPatterns.length > 0
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const artifactChecks = [
|
|
96
|
+
{ name: 'surfacePlan', present: surfacePlan.length > 0 },
|
|
97
|
+
{ name: 'componentGraphNodes', present: componentGraphNodes.length > 1 },
|
|
98
|
+
{ name: 'componentGraphEdges', present: componentGraphEdges.length > 0 },
|
|
99
|
+
{
|
|
100
|
+
name: 'contentPriorityMap',
|
|
101
|
+
present: ['primary', 'secondary', 'deferred'].every((bucketKey) => Array.isArray(contentPriorityMap?.[bucketKey]) && contentPriorityMap[bucketKey].length > 0),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'viewportMutationPlan',
|
|
105
|
+
present: ['mobile', 'tablet', 'desktop'].every((viewportKey) => hasViewportMutationEntry(viewportKey)),
|
|
106
|
+
},
|
|
107
|
+
{ name: 'interactionStateMatrix', present: interactionStateMatrix.length > 0 },
|
|
108
|
+
{ name: 'taskFlowNarrative', present: taskFlowNarrative.length > 1 },
|
|
109
|
+
{ name: 'signatureMoveRationale', present: String(designExecutionHandoff.signatureMoveRationale || '').trim().length > 0 },
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const presentArtifacts = artifactChecks.filter((artifactCheck) => artifactCheck.present).map((artifactCheck) => artifactCheck.name);
|
|
113
|
+
const missingArtifacts = artifactChecks.filter((artifactCheck) => !artifactCheck.present).map((artifactCheck) => artifactCheck.name);
|
|
114
|
+
const implementationGuardrails = designExecutionHandoff.implementationGuardrails
|
|
115
|
+
&& typeof designExecutionHandoff.implementationGuardrails === 'object'
|
|
116
|
+
? designExecutionHandoff.implementationGuardrails
|
|
117
|
+
: {};
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
present: Object.keys(designExecutionHandoff).length > 0,
|
|
121
|
+
version: typeof designExecutionHandoff.version === 'string' ? designExecutionHandoff.version : null,
|
|
122
|
+
handoffReady: (typeof designExecutionHandoff.version === 'string' && designExecutionHandoff.version === 'ui-handoff-v1')
|
|
123
|
+
&& missingArtifacts.length === 0
|
|
124
|
+
&& implementationGuardrails.requireBuildFromHandoff === true
|
|
125
|
+
&& implementationGuardrails.requireGapNotesBeforeFallback === true
|
|
126
|
+
&& implementationGuardrails.forbidGenericLayoutFallbackWithoutReason === true,
|
|
127
|
+
artifactCount: presentArtifacts.length,
|
|
128
|
+
presentArtifacts,
|
|
129
|
+
missingArtifacts,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function summarizeDesignExecutionPolicy(designIntentContent) {
|
|
134
|
+
const designExecutionPolicy = designIntentContent?.designExecutionPolicy
|
|
135
|
+
&& typeof designIntentContent.designExecutionPolicy === 'object'
|
|
136
|
+
? designIntentContent.designExecutionPolicy
|
|
137
|
+
: {};
|
|
138
|
+
|
|
139
|
+
const requiredCapabilities = DESIGN_EXECUTION_REQUIRED_CAPABILITIES.map((capability) => ({
|
|
140
|
+
name: capability,
|
|
141
|
+
enabled: designExecutionPolicy[capability] === true,
|
|
142
|
+
}));
|
|
143
|
+
const enabledCapabilities = requiredCapabilities
|
|
144
|
+
.filter((capability) => capability.enabled)
|
|
145
|
+
.map((capability) => capability.name);
|
|
146
|
+
const missingCapabilities = requiredCapabilities
|
|
147
|
+
.filter((capability) => !capability.enabled)
|
|
148
|
+
.map((capability) => capability.name);
|
|
149
|
+
const semanticReviewFocus = normalizeStringArray(designExecutionPolicy.semanticReviewFocus);
|
|
150
|
+
const representationStrategy = typeof designExecutionPolicy.representationStrategy === 'string'
|
|
151
|
+
? designExecutionPolicy.representationStrategy
|
|
152
|
+
: null;
|
|
153
|
+
const repoEvidenceAvailable = hasRepoEvidenceSummary(designIntentContent);
|
|
154
|
+
const structuredInspectionAvailable = hasStructuredInspectionEvidence(designIntentContent);
|
|
155
|
+
const screenshotDependencyForbidden = designExecutionPolicy.forbidScreenshotDependency === true;
|
|
156
|
+
const handoffFormatVersion = typeof designExecutionPolicy.handoffFormatVersion === 'string'
|
|
157
|
+
? designExecutionPolicy.handoffFormatVersion
|
|
158
|
+
: null;
|
|
159
|
+
const handoffSummary = summarizeDesignExecutionHandoff(designIntentContent);
|
|
160
|
+
const policyPresent = Object.keys(designExecutionPolicy).length > 0;
|
|
161
|
+
const contractReady = policyPresent
|
|
162
|
+
&& representationStrategy === 'surface-plan-v1'
|
|
163
|
+
&& handoffFormatVersion === 'ui-handoff-v1'
|
|
164
|
+
&& missingCapabilities.length === 0
|
|
165
|
+
&& semanticReviewFocus.length >= 4
|
|
166
|
+
&& screenshotDependencyForbidden
|
|
167
|
+
&& handoffSummary.handoffReady
|
|
168
|
+
&& repoEvidenceAvailable;
|
|
169
|
+
|
|
170
|
+
const notes = [];
|
|
171
|
+
if (!policyPresent) {
|
|
172
|
+
notes.push('designExecutionPolicy is missing from docs/design-intent.json.');
|
|
173
|
+
}
|
|
174
|
+
if (representationStrategy !== 'surface-plan-v1') {
|
|
175
|
+
notes.push('Structured design execution should declare representationStrategy "surface-plan-v1".');
|
|
176
|
+
}
|
|
177
|
+
if (handoffFormatVersion !== 'ui-handoff-v1') {
|
|
178
|
+
notes.push('Structured design execution should declare handoffFormatVersion "ui-handoff-v1".');
|
|
179
|
+
}
|
|
180
|
+
if (missingCapabilities.length > 0) {
|
|
181
|
+
notes.push(`Structured design execution is missing required capabilities: ${missingCapabilities.join(', ')}.`);
|
|
182
|
+
}
|
|
183
|
+
if (semanticReviewFocus.length < 4) {
|
|
184
|
+
notes.push('Structured design execution should declare semantic review focus dimensions before UI implementation review.');
|
|
185
|
+
}
|
|
186
|
+
if (!screenshotDependencyForbidden) {
|
|
187
|
+
notes.push('Structured design execution must explicitly forbid screenshot dependency as a baseline requirement.');
|
|
188
|
+
}
|
|
189
|
+
if (!handoffSummary.handoffReady) {
|
|
190
|
+
notes.push(`Structured design handoff is incomplete: ${handoffSummary.missingArtifacts.join(', ') || 'missing or invalid handoff metadata'}.`);
|
|
191
|
+
}
|
|
192
|
+
if (!repoEvidenceAvailable) {
|
|
193
|
+
notes.push('repoEvidence.designEvidenceSummary is missing or unreadable.');
|
|
194
|
+
} else if (!structuredInspectionAvailable) {
|
|
195
|
+
notes.push('repoEvidence.designEvidenceSummary.structuredInspection is missing; class and inline-style evidence will stay lower-confidence.');
|
|
196
|
+
}
|
|
197
|
+
if (notes.length === 0) {
|
|
198
|
+
notes.push('Structured design execution policy is present and ready for contract review.');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
policyPresent,
|
|
203
|
+
representationStrategy,
|
|
204
|
+
contractReady,
|
|
205
|
+
screenshotDependencyForbidden,
|
|
206
|
+
repoEvidenceAvailable,
|
|
207
|
+
structuredInspectionAvailable,
|
|
208
|
+
handoffPresent: handoffSummary.present,
|
|
209
|
+
handoffVersion: handoffSummary.version,
|
|
210
|
+
handoffReady: handoffSummary.handoffReady,
|
|
211
|
+
handoffArtifactCount: handoffSummary.artifactCount,
|
|
212
|
+
presentHandoffArtifacts: handoffSummary.presentArtifacts,
|
|
213
|
+
missingHandoffArtifacts: handoffSummary.missingArtifacts,
|
|
214
|
+
repoEvidenceSummaryVersion: repoEvidenceAvailable
|
|
215
|
+
? String(designIntentContent.repoEvidence.designEvidenceSummary.summaryVersion || '')
|
|
216
|
+
: null,
|
|
217
|
+
requiredCapabilities: requiredCapabilities.map((capability) => capability.name),
|
|
218
|
+
enabledCapabilities,
|
|
219
|
+
missingCapabilities,
|
|
220
|
+
semanticReviewFocus,
|
|
221
|
+
notes,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function summarizeReviewRubric(designIntentContent) {
|
|
226
|
+
const reviewRubric = designIntentContent?.reviewRubric && typeof designIntentContent.reviewRubric === 'object'
|
|
227
|
+
? designIntentContent.reviewRubric
|
|
228
|
+
: {};
|
|
229
|
+
|
|
230
|
+
const dimensions = Array.isArray(reviewRubric.dimensions)
|
|
231
|
+
? reviewRubric.dimensions
|
|
232
|
+
.map((dimension) => ({
|
|
233
|
+
key: String(dimension?.key || '').trim(),
|
|
234
|
+
blockingByDefault: dimension?.blockingByDefault === true,
|
|
235
|
+
question: String(dimension?.question || '').trim(),
|
|
236
|
+
}))
|
|
237
|
+
.filter((dimension) => Boolean(dimension.key))
|
|
238
|
+
: [];
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
version: typeof reviewRubric.version === 'string' ? reviewRubric.version : null,
|
|
242
|
+
dimensions,
|
|
243
|
+
genericityAutoFail: reviewRubric.genericityAutoFail === true,
|
|
244
|
+
genericitySignals: normalizeStringArray(reviewRubric.genericitySignals),
|
|
245
|
+
validBoldSignals: normalizeStringArray(reviewRubric.validBoldSignals),
|
|
246
|
+
forbiddenPatterns: normalizeStringArray(designIntentContent?.forbiddenPatterns),
|
|
247
|
+
reportingRules: reviewRubric.reportingRules && typeof reviewRubric.reportingRules === 'object'
|
|
248
|
+
? {
|
|
249
|
+
mustExplainGenericity: reviewRubric.reportingRules.mustExplainGenericity === true,
|
|
250
|
+
mustSeparateTasteFromFailure: reviewRubric.reportingRules.mustSeparateTasteFromFailure === true,
|
|
251
|
+
contractFidelityOverridesPersonalTaste: reviewRubric.reportingRules.contractFidelityOverridesPersonalTaste === true,
|
|
252
|
+
}
|
|
253
|
+
: {
|
|
254
|
+
mustExplainGenericity: false,
|
|
255
|
+
mustSeparateTasteFromFailure: false,
|
|
256
|
+
contractFidelityOverridesPersonalTaste: false,
|
|
257
|
+
},
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { extname } from 'node:path';
|
|
5
|
+
import { REPOSITORY_ROOT, UI_FILE_EXTENSIONS } from './constants.mjs';
|
|
6
|
+
|
|
7
|
+
export function detectCiProvider() {
|
|
8
|
+
if (process.env.GITHUB_ACTIONS === 'true') {
|
|
9
|
+
return 'github';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (process.env.GITLAB_CI === 'true') {
|
|
13
|
+
return 'gitlab';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return 'local';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function collectGitDiff(baseSha, headSha) {
|
|
20
|
+
const execOptions = {
|
|
21
|
+
cwd: REPOSITORY_ROOT,
|
|
22
|
+
encoding: /** @type {'utf-8'} */ ('utf-8'),
|
|
23
|
+
maxBuffer: 1024 * 1024 * 8,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return execSync(`git diff "${baseSha}...${headSha}"`, execOptions);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function collectGitChangedFiles(baseSha, headSha) {
|
|
30
|
+
const execOptions = {
|
|
31
|
+
cwd: REPOSITORY_ROOT,
|
|
32
|
+
encoding: /** @type {'utf-8'} */ ('utf-8'),
|
|
33
|
+
maxBuffer: 1024 * 1024 * 2,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const output = execSync(`git diff --name-only "${baseSha}...${headSha}"`, execOptions);
|
|
37
|
+
return output
|
|
38
|
+
.split(/\r?\n/u)
|
|
39
|
+
.map((filePath) => filePath.trim())
|
|
40
|
+
.filter(Boolean);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function collectPullRequestDiff() {
|
|
44
|
+
if (process.env.PR_DIFF) {
|
|
45
|
+
return process.env.PR_DIFF;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const githubBaseSha = process.env.GITHUB_BASE_SHA;
|
|
49
|
+
const githubHeadSha = process.env.GITHUB_HEAD_SHA ?? 'HEAD';
|
|
50
|
+
if (githubBaseSha) {
|
|
51
|
+
return collectGitDiff(githubBaseSha, githubHeadSha);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const gitlabBaseSha = process.env.CI_MERGE_REQUEST_DIFF_BASE_SHA;
|
|
55
|
+
const gitlabHeadSha = process.env.CI_COMMIT_SHA ?? 'HEAD';
|
|
56
|
+
if (gitlabBaseSha) {
|
|
57
|
+
return collectGitDiff(gitlabBaseSha, gitlabHeadSha);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
return execSync('git diff HEAD~1 HEAD', {
|
|
62
|
+
cwd: REPOSITORY_ROOT,
|
|
63
|
+
encoding: /** @type {'utf-8'} */ ('utf-8'),
|
|
64
|
+
maxBuffer: 1024 * 1024 * 8,
|
|
65
|
+
});
|
|
66
|
+
} catch {
|
|
67
|
+
try {
|
|
68
|
+
const emptyTreeSha = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
|
|
69
|
+
return execSync(`git diff "${emptyTreeSha}" HEAD`, {
|
|
70
|
+
cwd: REPOSITORY_ROOT,
|
|
71
|
+
encoding: /** @type {'utf-8'} */ ('utf-8'),
|
|
72
|
+
maxBuffer: 1024 * 1024 * 8,
|
|
73
|
+
});
|
|
74
|
+
} catch {
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function collectChangedFiles() {
|
|
81
|
+
if (process.env.PR_DIFF) {
|
|
82
|
+
const filePathSet = new Set();
|
|
83
|
+
for (const diffHeaderMatch of process.env.PR_DIFF.matchAll(/^diff --git a\/(.+?) b\/(.+)$/gm)) {
|
|
84
|
+
filePathSet.add(diffHeaderMatch[2]);
|
|
85
|
+
}
|
|
86
|
+
return Array.from(filePathSet);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const githubBaseSha = process.env.GITHUB_BASE_SHA;
|
|
90
|
+
const githubHeadSha = process.env.GITHUB_HEAD_SHA ?? 'HEAD';
|
|
91
|
+
if (githubBaseSha) {
|
|
92
|
+
return collectGitChangedFiles(githubBaseSha, githubHeadSha);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const gitlabBaseSha = process.env.CI_MERGE_REQUEST_DIFF_BASE_SHA;
|
|
96
|
+
const gitlabHeadSha = process.env.CI_COMMIT_SHA ?? 'HEAD';
|
|
97
|
+
if (gitlabBaseSha) {
|
|
98
|
+
return collectGitChangedFiles(gitlabBaseSha, gitlabHeadSha);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const output = execSync('git diff --name-only HEAD~1 HEAD', {
|
|
103
|
+
cwd: REPOSITORY_ROOT,
|
|
104
|
+
encoding: /** @type {'utf-8'} */ ('utf-8'),
|
|
105
|
+
maxBuffer: 1024 * 1024 * 2,
|
|
106
|
+
});
|
|
107
|
+
return output.split(/\r?\n/u).map((filePath) => filePath.trim()).filter(Boolean);
|
|
108
|
+
} catch {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function isUiRelevantFilePath(filePath) {
|
|
114
|
+
const normalizedFilePath = String(filePath || '').replace(/\\/g, '/').toLowerCase();
|
|
115
|
+
const fileExtension = extname(normalizedFilePath);
|
|
116
|
+
|
|
117
|
+
if (!UI_FILE_EXTENSIONS.has(fileExtension)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
normalizedFilePath.startsWith('src/')
|
|
123
|
+
|| normalizedFilePath.startsWith('app/')
|
|
124
|
+
|| normalizedFilePath.startsWith('pages/')
|
|
125
|
+
|| normalizedFilePath.startsWith('components/')
|
|
126
|
+
|| normalizedFilePath.startsWith('styles/')
|
|
127
|
+
|| normalizedFilePath.includes('/components/')
|
|
128
|
+
|| normalizedFilePath.includes('/screens/')
|
|
129
|
+
|| normalizedFilePath.includes('/layouts/')
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { MAX_DIFF_CHARS } from './constants.mjs';
|
|
4
|
+
|
|
5
|
+
export function buildSystemPrompt() {
|
|
6
|
+
return [
|
|
7
|
+
'You are a Principal UI/UX Design Reviewer.',
|
|
8
|
+
'Compare the changed UI code against the provided design contract.',
|
|
9
|
+
'Treat docs/design-intent.json as the machine-readable source of truth.',
|
|
10
|
+
'Treat docs/DESIGN.md as explanatory context, not a generic style guide.',
|
|
11
|
+
'Treat designExecutionPolicy as the execution contract for how the UI must be planned, structured, and reviewed.',
|
|
12
|
+
'Treat designExecutionHandoff as the explicit bridge between design intent and implementation decisions.',
|
|
13
|
+
'Treat reviewRubric as the stable scoring frame for distinctiveness, contract fidelity, visual consistency, heuristic UX quality, and motion discipline.',
|
|
14
|
+
'Use repoEvidence.designEvidenceSummary as implementation evidence when deciding whether the diff follows the intended system.',
|
|
15
|
+
'Do not reward generic SaaS defaults or popular template patterns.',
|
|
16
|
+
'Do not penalize originality when the implementation still aligns with the contract.',
|
|
17
|
+
'Purposeful motion is allowed and can improve quality. Only flag motion when it drifts from the contract, ignores reduced-motion expectations, or adds avoidable performance/accessibility risk.',
|
|
18
|
+
'Only flag drift when there is a clear mismatch with the contract, accessibility non-negotiables, or cross-viewport adaptation rules.',
|
|
19
|
+
'Treat WCAG 2.2 AA failures as hard accessibility drift.',
|
|
20
|
+
'Treat APCA as advisory perceptual tuning only. Do not set blocking solely because APCA indicates a stronger readability adjustment when WCAG hard requirements still pass.',
|
|
21
|
+
'Check focus visibility, focus appearance, target size, keyboard access, accessible authentication, and status or dynamic state access when the diff touches those surfaces.',
|
|
22
|
+
'This audit always runs in advisory mode for this repository workflow.',
|
|
23
|
+
'Focus on color intent, typographic hierarchy, responsive re-layout, purposeful motion, component morphology across states, interaction behavior, and genericity drift.',
|
|
24
|
+
'If you call something generic, explain the specific genericity signal or anti-pattern that caused that judgment.',
|
|
25
|
+
'Separate taste from failure. A bold design that follows the contract must not be penalized only because it is unusual.',
|
|
26
|
+
'Return ONLY one JSON object on a single line prefixed with JSON_VERDICT:.',
|
|
27
|
+
'Schema:',
|
|
28
|
+
'{"alignmentScore": number|null, "genericityAssessment": {"status": "distinctive|mixed|generic|unclear", "reason": string}, "tasteVsFailureSeparated": boolean, "rubricBreakdown": [{"dimension": string, "score": number|null, "verdict": "strong|acceptable|weak|unclear", "reason": string, "blocking": boolean}], "notes": string[], "findings": [{"area": string, "severity": "high|medium|low", "problem": string, "evidence": string, "requiredAction": string, "blockingRecommended": boolean}]}',
|
|
29
|
+
].join('\n');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function buildUserMessage(designIntentContent, designGuideContent, diffContent, changedUiFiles, designExecutionSummary) {
|
|
33
|
+
const truncatedDiff = diffContent.length > MAX_DIFF_CHARS
|
|
34
|
+
? `${diffContent.slice(0, MAX_DIFF_CHARS)}\n\n[DIFF TRUNCATED - ${diffContent.length - MAX_DIFF_CHARS} additional characters omitted]`
|
|
35
|
+
: diffContent;
|
|
36
|
+
|
|
37
|
+
return [
|
|
38
|
+
'## Changed UI Files',
|
|
39
|
+
changedUiFiles.length > 0 ? changedUiFiles.map((filePath) => `- ${filePath}`).join('\n') : '- none',
|
|
40
|
+
'',
|
|
41
|
+
'## design-intent.json',
|
|
42
|
+
'```json',
|
|
43
|
+
JSON.stringify(designIntentContent, null, 2),
|
|
44
|
+
'```',
|
|
45
|
+
'',
|
|
46
|
+
'## Review Rubric',
|
|
47
|
+
'```json',
|
|
48
|
+
JSON.stringify(designIntentContent?.reviewRubric || null, null, 2),
|
|
49
|
+
'```',
|
|
50
|
+
'',
|
|
51
|
+
'## Structured Design Handoff',
|
|
52
|
+
'```json',
|
|
53
|
+
JSON.stringify(designIntentContent?.designExecutionHandoff || null, null, 2),
|
|
54
|
+
'```',
|
|
55
|
+
'',
|
|
56
|
+
'## DESIGN.md',
|
|
57
|
+
'```md',
|
|
58
|
+
designGuideContent.trim() || '(missing DESIGN.md)',
|
|
59
|
+
'```',
|
|
60
|
+
'',
|
|
61
|
+
'## Structured Design Execution Summary',
|
|
62
|
+
'```json',
|
|
63
|
+
JSON.stringify(designExecutionSummary, null, 2),
|
|
64
|
+
'```',
|
|
65
|
+
'',
|
|
66
|
+
'## UI Diff',
|
|
67
|
+
'```diff',
|
|
68
|
+
truncatedDiff.trim() || '(no UI diff)',
|
|
69
|
+
'```',
|
|
70
|
+
'',
|
|
71
|
+
'Judge alignment to the contract. Avoid aesthetic bias toward generic web trends or toward motionless/static outputs.',
|
|
72
|
+
].join('\n');
|
|
73
|
+
}
|